Ruby on Rails: Get next item in model - ruby-on-rails

Say I have simply ran rails g scaffold book name:string about:text On the 'show' view, how would I implement a button to go to the next item in the model.
I can't simply do #next = #book.id + 1 because if #book.id = 2 (for example) and I clicked destroy on the book with an id of 3. It would result in a broken page.

You can do:
#next = Book.first(:conditions => ['id > ?', #book.id], :order => 'id ASC')
Remember to check whever #next is not nil
To be even cooler, you can create a method in your model like so:
def next
Book.first(:conditions => ['id > ?', self.id], :order => 'id ASC')
end
then if you have #book you should be able to invoke it like
#book.next
haven't written anything in RoR recently but this looks reasonable to me;)

Related

How can I sort records ordered by datetime?

#comments has 10 records sorted by id ASC now.
I wanted to reverse order simply, so I coded #comments = #comments.reverse
But I get this error message
ActionView::Template::Error (undefined method `total_count'
<%= page_entries_info(#comments, :entry_name => 'comment').html_safe %>
If I take off reverse and leave it as #comments = #comments, there won't be any problem.
Why? and How can I sort it ordered by created_at DESC?
#comments = Comment.where(:user_id => user_ids, :commentable_type => commentable)
if params[:page].blank?
params[:page] = ((#comments.count - 1)/10) + 1
#comments = #comments.page(params[:page]).per(10)
else
#comments = #comments.page(params[:page]).per(10)
end
#comments = #comments.reverse
You're receiving
ActionView::Template::Error (undefined method `total_count'
because #comments.reverse returns a plain array. You need a Relation object that has the pagination functionality. The best way to accomplish your sorting needs is to create a scope in your comments model:
class Comment < ActiveRecord::Base
scope :reversed, -> { order 'created_at DESC' }
end
Then instantiate your comments by calling the scope:
#comments = Comment.reversed
You can call the page method on that #comments collection and the rest should work. You can chain your where like you had it before, so it would look like:
Comment.reversed.where(:user_id => user_ids, :commentable_type => commentable)
#comments = Comment.where(:user_id => user_ids, :commetable_type => commentable).order("created_at DESC").page(params[:page]).per(10)
ALso, your page calculation is bad can can result in float/fractional numbers. If params[:page] is blank, just default to page 1 for the first page.

RoR | how to find the next and previous based on a date

I'm trying to look up 1 instance of a model via a slug, which I can do via
#project = Project.where(:slug => params[:id]).first
But once I get the instance I want to find the next and previous instances of the model from a order of a column called publish which is a date.
how would I get the slug only of the previous and next instance of my arbitrary order by column?
::Edit::
Adding this to my modeled worked.
def previous(offset = 0)
self.class.select('slug').first(:conditions => ['published < ?', self.id], :limit => 1, :offset => offset, :order => "published DESC")
end
def next(offset = 0)
self.class.select('slug').first(:conditions => ['published > ?', self.id], :limit => 1, :offset => offset, :order => "published ASC")
end
I found this solution at Next, Previous Records Using Named Scope
Assuming your publish dates are unique something like this ought to work:
#project = Project.where(:slug => params[:id]).first
#prev_project = Project.where( "publish < ?", #project.publish ).
order( "publish DESC" ).
first
#next_project = Project.where( "publish > ?", #project.publish ).
order( "publish" ).
first
For #prev_project the where clause indicates all Projects before #project, the order lists them latest first (which puts the one right before #project at the top) and first limits the query to one result, i.e. the previous Project. The query for #next_project is the same but reversed. If publish isn't unique you'll have to sort on a second criterium as well.
However, you may want to consider not reinventing the wheel and using the very common gem acts_as_list, which would make this as simple as #project.higher_item and #project.lower_item.

Using Paginate with constructed query in active record

I'm trying to do something along the following lines:
if start_date?
query = where("datetime > :start_date", :start_date => start_date)
end
if end_date?
query = where("datetime <= :end_date", :end_date => end_date)
end
if client
query = where("client.name LIKE :client", :client => client)
end
if staff
query = where("staff.name LIKE :staff", :staff => staff)
end
paginate :per_page => 20, :page => page,
:conditions => query,
:order => 'datetime'
I can't find any examples though of how to build up a query and use it along with paginate plugin. Of course the query isn't correct yet as I've got to figure out how to do the joins so I can query against the other properties in other tables, but other than that, how do you build up queries to use in the conditions?
You want to put the .paginate call on the end of the chain of where calls, like this (all one line, split up for readability)
SomeClass.where("datetime > :start_date", :start_date=>start_date).
where("datetime < :end_date", :end_date=>end_date).
paginate(:per_page=>20,:page=>page)
If you want to conditionally add the conditions, you can do that too, like:
x = SomeClass
x = x.where("datetime > :start_date", :start_date=>start_date) if start_date
x = x.where("datetime < :end_date", :end_date=>end_date) if end_date
x.paginate(:per_page=>20,:page=>page)
You can try this if you installed kaminari gem :
ModelName.where( your_query ).page(params[:page]).per(50)
.where() returns ActiveRecord::Relation
.paginate()'s :conditions parameter has to be an array of conditions as if it was an argument of .where().
You should fix your code as next:
if start_date?
query = ["datetime > :start_date", :start_date => start_date]
end
etc.
ActiveRecord Relation doesn't have paginate method

Adding another condition to existing named_scope

I am working on a Rails 2.3.9 app and my question involves both a self referencial relationship and a named_scope. This application allows users to log and share workouts. A workout can be public or private and that is designated by #workout.public == 1.
I allow users to 'follow' people. So on a current_user's dashboard I display all public workouts from users that current_user follows with the following code:
/dashboard/index.html.erb
<% current_user.friends_workouts.each do |workout| %>
<%= link_to (workout.title), workout %> <br/>
by <%= link_to (workout.user.username), workout.user %> - <%= time_ago_in_words(workout.created_at)%> ago</p>
<% end %>
user.rb
def friends_workouts
#friends_workouts ||= Workout.current.public_workouts.find_all_by_user_id(self.friends.map(&:id), :order => "created_at DESC", :limit => 3)
end
workout.rb
named_scope :public_workouts, :conditions => {:public => 1 }, :order => "created_at DESC"
I now want to add a condition to this scope as I am adding another level of sharing. Users can associate to a "box" (a gym really) through a "membership" model. So if the current_user belongs_to the same "box" as a user they follow, they should not only see the workouts marked public but also workouts where #workout.box_only == 1.
How can I affect the above to include all public workouts from followed users AND workouts from followed users where #workout.box_only == 1 and #workout.user.membership.box_id == current_user.membership.box_id. I know that syntax is incorrect but you get my point (hopefully).
UPDATE:
It also needs to be considered that :public_workouts is being called from pages that don't require a logged_in? user so in that case if the scope is trying to reference current_user it will throw an error.
UPDATE 2:
:user has_many :memberships
I believe something like the following should do it for you:
named_scope :public_workouts,
:joins => ", user, membership"
:conditions =>
"workouts.public = 1 or
membership.box_id = #{current_user.membership.box_id}",
:group => "workouts.id",
:order => "workouts.created_at DESC"
You would have to play around with this for a bit. The hard part every time I try something like this is to get the OR conditions correct. You want to get all public and those where the joined membership.box_id matches regardless of public being 1.
Edit: Admittedly this is perhaps not the most ruby way of building a query and I haven't tested it properly but something like below could also be made to work.
def self.public_workouts
query = Workout.joins(:user => { :membership })
if current_user
query.where('memberships.box_id = ? or workouts.public = 1', current_user.membership.box_id) unless current_user.membership.box_id.nil?
else
query.where('workouts.public = 1')
end
query.group('workouts.id')
query.order("workouts.created_at DESC")
return query
end
Edit2
Another alternative could be to create two separate scopes and create a class method that combines the two scopes. This class method would then be used in the view.
named_scope :box_workouts,
:joins => ", user, membership"
:conditions => "memberships.box_id = #{current_user.membership.box_id}"
:group => "workouts.id",
:order => "workouts.created_at DESC",
:select "workouts"
named_scope :public_workouts,
:conditions => :public => 1
:order => "workouts.created_at DESC"
def self.public_box_workouts
return box_workouts.merge(public_workouts).limit(3) if current_user
return public_workouts.limit(3)
end
Edit3 Not so hard, I believe something like below will work.
def self.box_and_public_workouts(user)
return public_workouts if user.nil? or user.memberships.blank?
return public_workouts + box_workouts(user.memberships.map(&:box_id))
end
named_scope :box_workouts, lambda { |box_ids| { :conditions => ['box_id IN (?)', box_ids], :order => 'created_at DESC' } }
Apologies for taking so long. I was missing confused with how the "old" way of querying the database. I went right for Rails3 :)
Anyway, I didn't want to commit anything so I tried to fork it to send a pull request but github is being rude to me tonight. Might just copy from here then.

Next, Previous Records Using Named Scope

I have a model for which I want to retrieve the next record(s) and previous record(s). I want to do this via a named_scope on the model, and also pass in as an argument the X number of next/previous records to return.
For example, let's say I have 5 records:
Record1
Record2
Record3
Record4
Record5
I want to be able to call Model.previous or Model.previous(1) to return Record2. Similarly, I want to be able to call Model.next or Model.next(1) to return Record4. As another example I want to be able to call Model.previous(2) to return Record3. I think you get the idea.
How can I accomplish this?
To implement something like 'Model.previous', the class itself would have to have a 'current' state. That would make sense if the 'current' record (perhaps in a publishing scheduling system?) was Record3 in your example, but your example doesn't suggest this.
If you want to take an instance of a model and get the next or previous record, the following is a simple example:
class Page < ActiveRecord::Base
def previous(offset = 0)
self.class.first(:conditions => ['id < ?', self.id], :limit => 1, :offset => offset, :order => "id DESC")
end
def next(offset = 0)
self.class.first(:conditions => ['id > ?', self.id], :limit => 1, :offset => offset, :order => "id ASC")
end
end
If so you could do something like:
#page = Page.find(4)
#page.previous
Also working would be:
#page.previous(1)
#page.next
#page.next(1)
Obviously, this assumes that the idea of 'next' and 'previous' is by the 'id' field, which probably wouldn't extend very well over the life of an application.
If you did want to use this on the class, you could perhaps extend this into a named scope that takes the 'current' record as an argument. Something like this:
named_scope :previous, lambda { |current, offset| { :conditions => ['id < ?', current], :limit => 1, :offset => offset, :order => "id DESC" }}
Which means you could call:
Page.previous(4,1)
Where '4' is the id of the record you want to start on, and 1 is the number you want to navigate back.
I added this feature into my gem by_star last month. It may have some other features you're interested in, so check it out.
It works by you having an existing record and then calling previous or next on that:
Page.find(:last).previous
I wrote a gem for doing this for arbitrary order conditions, order_query:
class Issue < ActiveRecord::Base
include OrderQuery
order_query :order_display, [
[:priority, %w(high medium low)],
[:valid_votes_count, :desc, sql: '(votes - suspicious_votes)'],
[:updated_at, :desc],
[:id, :desc]
]
def valid_votes_count
votes - suspicious_votes
end
end
Issue.order_display #=> ActiveRecord::Relation<...>
Issue.reverse_order_display #=> ActiveRecord::Relation<...>
# get the order object, scope default: Post.all
p = Issue.find(31).order_list(scope) #=> OrderQuery::RelativeOrder<...>
p.before #=> ActiveRecord::Relation<...>
p.previous #=> Issue<...>
p.position #=> 5
p.next #=> Issue<...>
p.after #=> ActiveRecord::Relation<...>

Resources