1

Photos Controller

@photos = Photo.all.paginate(page: params[:page], per_page: 9)

I want my view as follows along the top:

[Today] [Yesterday] [This Week] [Last Week] [Popular]

<% @photos.in_groups_of(6, false).each do |group| %> 
  <% group.each do |photo| %>
     <%= image_tag photo.thumb.url %>
     <%= photo.title %> 
  <% end %>
<% end %>  

and when the link_to yesterday is clicked, i want to substitute the following instance variable for @photos in the view:

 @photos_yesterday = Photo.where("DATE(created_at) = DATE(?)", 1.day.ago).paginate(page: params[:page], per_page: 9)
Timmy Von Heiss
  • 2,160
  • 17
  • 39

1 Answers1

3

To pass params in a GET request that are not a part of the path in Rails (and in general) you use the query string:

/photos?page=2&foo=bar

One of the simplest ways to add query parameters is by passing a hash to the link helper:

link_to( "More photos", photos_path(page: 2, foo: 'bar') )

To query for records in a certain span of time you would use .where with a range:

@this_week = Photo.where(created_at: [Date.today.beginning_of_week..Time.now])
# SELECT "photos".* FROM "photos" 
# WHERE ( "photos"."created_at" BETWEEN '2016-05-22' AND '2016-05-26 07:43:49.331188')

While you could pass times like so:

link_to( "More photos", photos_path(from: Date.today.beginning_of_week, to: Time.now) )

Its not very efficient from a caching point of view since it requires the view to be rendered for each request.

Instead you might want to have a set of keywords.

Lets setup a class that gives us a range from keywords:

# lib/time_range.rb
class TimeRange < Range

  KEYWORDS = [:today, :yesterday, :this_week, 
     :last_week, :this_month, :last_month, 
     :this_year, :last_year]


  # @param [Symbol] keyword  such as :today, :yesterday :this_week, :last_week ...
  def initalize(keyword)
    keyword = keyword.to_sym
    # the * (splat) operator turns the array into a list of arguments
    super( *from_beginning_to_end(base_time(keyword), unit_of_time(keyword)) )
  end

  self.valid?(keyword)
    KEYWORDS.include?(keyword.to_sym)
  end

  private 

    # strips this_ or last_ from the keyword
    def unit_of_time(keyword)
      [:today, :yesterday].include?(keyword) ? 'day' : keyword[5..-1]
    end

    def base_time(keyword)
      if keyword == :yesterday || keyword.to_s.starts_with? 'last_'
        DateTime.now.send(keyword)
      else
        DateTime.now
      end
    end

    # creates an array by dynamically calling for example
    # `beginning_of_week` and `beginning_of_week` on the base time.
    def from_beginning_to_end(base, unit)
      [
         base.send("beginning_of_#{unit}"),
         base.send("end_of_#{unit}"),
      ]
    end
end

Now you can do:

link_to 'Todays photos', photos_path( keyword: 'today' )
link_to 'Last years photos', photos_path( keyword: 'last_year' )

@photos = Photo.all
@photos = @photos.where(
  created_at: TimeRange.new(params[:keyword])
) if TimeRange.valid?(params[:keyword])

chaining scopes.

In active record you can chain scopes together:

Photo.all.where(foo: 'bar').where(baz: 3) 

You can use that together with params to build a set of filters:

 @photos = Photo.all

 if params[:orientation]
   if params[:orientation] == 'landscape'
     @photos = @photos.landscape
   else if params[:orientation] == 'p'
     @photos = @photos.portrait
   end
end

Etc - can get a bit messy if you have a lot of filter. So don't be afraid to create scopes in your models.

max
  • 96,212
  • 14
  • 104
  • 165
  • See http://stackoverflow.com/questions/19098663/auto-loading-lib-files-in-rails-4 if you get `NameError: uninitialized constant TimeRange` errors. – max May 26 '16 at 10:27
  • My photo model also contains enum, for landscape, people, street. So I have Photo.landscape Photo.people Photo.street... so how can I setup my page so that someone can go from the initial index `@photos = Photo.all to @photos = Photo.landscape` and then filter that with the time keyword method? do i have to have separate views for each enum? thanks – Timmy Von Heiss May 26 '16 at 19:48
  • Edited with a bit of explanation about chaining scopes. – max May 26 '16 at 21:04
  • "do i have to have separate views for each enum?". No not necessarily - but you may want to have a separate controller for categories for example. Don't try to shoehorn everything into a single model / controller. – max May 26 '16 at 21:07
  • http://www.justinweiss.com/articles/search-and-filter-rails-models-without-bloating-your-controller/ – max May 26 '16 at 21:41
  • looks like something is wrong with def self.valid?(keyword)... I am getting the following error: `NoMethodError (undefined method `to_sym' for nil:NilClass):` and `lib/time_range.rb:16:in `valid?'` thanks – Timmy Von Heiss May 27 '16 at 16:29