Next element in array of active record objects - ruby-on-rails

Ruby on Rails Next/Previous Post
I have an index page - posts#index which has links to the show pages of each post. Each post references a particular topic and may have been created at different times, so the id's of the posts aren't necessarily sequential for all the posts in that topic. I want to be able to go to the next & previous posts for a particular topic.
In my PostsController I've got instance variables for all the posts for a topic and the particular topic being shown
def index
#topic = Topic.find(params[:topic_id])
#posts = #topic.posts.all
end
def show
#topic = Topic.find(params[:topic_id])
#posts = #topic.posts.all
#post = #topic.posts.find(params[:id])
end
#posts is an array of active record objects
#posts => #<ActiveRecord::AssociationRelation [
#<Post id: 73, topic_id: 3>,
#<Post id: 74, topic_id: 3>,
#<Post id: 76, topic_id: 3>,
#<Post id: 77, topic_id: 3>,
#<Post id: 91, topic_id: 3>]
If this was an array of ruby objects I could use #posts.index(#post) to find the id of a particular element of the array and then do either index + 1 or index - 1 to traverse the array of posts, but .index is a different method in active record. If I'm on the show page for any given post in a particular topic, Post id: 76 for example, how can I go to link_to Post id: 74 (next) and link_to Post id: 77 (previous) in a way that work for any post in a particular topic?

You can simply do the following:
def show
#topic = Topic.find(params[:topic_id])
posts = #topic.posts.order('posts.id ASC')
#post = posts.find(params[:id])
#previous_post = posts.where('posts.id < ?', #post.id).first
#next_post = posts.where('posts.id > ?', #post.id).first
end

Try something like this:
Post.where("id > ?", 74).order(:id).first
Or, if you have some topics (not sure about your model), but it will look similar to this:
some_topic.posts.where("id > ?", 74).order(:id).first
Here the 74 is an ID of the current post.

There are a few gems to help. I have created one which is quit flexible because it works dynamically with any collection and its order (which is not the case when it's hardcoded like : Book.where("id > ?", 74).order(:id))
The gem is called nexter and it's basic usage is like this :
#books = Book.includes(:author).bestsellers.
order("genre", "authors.name", "published_at desc")
nexter = Nexter.wrap( #books, #books.find(params[:id]) )
nexter.previous
nexter.next
See the README for more info.

Related

Building queries into Rails API requests in the URL

I am trying to begin work on building some API functionality in a Rails app and I’m just playing around with the concepts in a simple app where a Movie has_many :reviews and Review belongs_to :movie and I’d like a url that looks like /api/movies?review=mark. Here is more data for context:
2.6.3 :005 > m
=> #<Movie id: 12, title: "Wonder Woman", rating: "PG-13", total_gross: 0.821847012e9, created_at: "2019-08-19 13:48:08", updated_at: "2019-08-19 13:48:08", description: "When a pilot crashes and tells of conflict in the ...", released_on: "2017-06-02", director: "Patty Jenkins", duration: "141 min", image_file_name: "wonder-woman.png">
2.6.3 :006 > m.reviews
Review Load (0.2ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."movie_id" = ? LIMIT ? [["movie_id", 12], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Review id: 1, name: "mark", stars: 3, comment: "blah", movie_id: 12, created_at: "2019-08-19 13:49:44", updated_at: "2019-08-19 13:49:44">]>
I have a basic api build so I can do /api/movies and will return json with all movies, but not sure where to go from here for query functionality in the a get request. Some one point me in the right direction?
You say you want:
all reviews for movies that have the name "mark"
In which case, it seems like you should be calling:
/api/reviews?review%5Bname%5D%5B%5D=mark
Which will give you request something like:
Started GET "/api/reviews?review%5Bname%5D%5B%5D=mark" for ::1 at 2019-08-21 17:40:28 -0700
Processing by Api::ReviewsController#index as HTML
Parameters: {review: {name: ['mark']}}
Then, in your Api::ReviewsController, you could do something like:
class Api::ReviewsController < ApplicationController
def index
if params[:review]
#reviews = Review.where(review_params)
else
#reviews = Review.all
end
... do some stuff like rendering json
end
private
def review_params
params.require(:review).permit(name: [])
end
end
And that will return all reviews for movies where Review has a name value of mark. You could pass in multiple names and get back records for multiple reviewers.
You might want to check queryko gem for building API queries

How to order by descending value in controller?

I am trying to show the list of jobs ordered by median_salary by descending order. So far, it seems to only take into account the first number of median_salary. So something like 900 is listed above 1000, even though the value of 1000 > 900.
homes_controller.rb:
def index
nyc_highest = Highestpaidjob.where("city = ?", "NYC")
#nyc_highest = nyc_highest.order("median_salary DESC")
end
index.html.erb:
<%= #nyc_highest.inspect %>
returns:
#<ActiveRecord::Relation [#<Highestpaidjob id: 11, job: "Architect, City Planner", median_salary: "95928.48", count: 237, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:17:17", updated_at: "2016-07-25 18:17:17">, #<Highestpaidjob id: 7, job: "Medical", median_salary: "170507.69", count: 128, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:09:30", updated_at: "2016-07-25 18:09:30">]>
It is listing 95928.48 as higher than 170507.69. Am I missing a condition?
I've looked at Best way to implement sort asc or desc in rails and it seemed to suggest the way I am currently writing the sort.
It's because your median_salary database field is string and it's sorted as string. You need to cast it to integer in order clause, or create a migration, which will change field datatype.
Difference between strings being sorting and floats being sorted:
irb(main):001:0> ["95928.48", "170507.69"].sort
=> ["170507.69", "95928.48"]
irb(main):002:0> [95928.48, 170507.69].sort
=> [95928.48, 170507.69]
In postgres your order clause should looks like this:
#nyc_highest = nyc_highest.order("CAST(median_salary as FLOAT) DESC")
As #teksisto said, you should change the median_salary for float or some type that accepts decimals. Also, I would suggest to create a scope on your model, something like
scope :nyc_highest, -> { where("city = ?", "NYC").order("median_salary DESC") }
on your Highestpaidjob model. Then, you just call Highestpaidjob.nyc_highest in any place of your application you like.
For changing the median_salary data type:
rails g migration ChangeMedianSalaryType
then edit your migration file:
class ChangeMedianSalaryType < ActiveRecord::Migration
def up
change_column :highestpaidjobs, :median_salary, :float
end
def down
change_column :highestpaidjobs, :median_slary, :string
end
end

Rails CollectionProxy randomly inserts in wrong order

I'm seeing some weird behaviour in my models, and was hoping someone could shed some light on the issue.
# user model
class User < ActiveRecord::Base
has_many :events
has_and_belongs_to_many :attended_events
def attend(event)
self.attended_events << event
end
end
# helper method in /spec-dir
def attend_events(host, guest)
host.events.each do |event|
guest.attend(event)
end
end
This, for some reason inserts the event with id 2 before the event with id 1, like so:
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 2, name: "dummy-event", user_id: 1>, #<Event id: 1, name: "dummy-event", user_id: 1>
But, when I do something seemlingly random - like for instance change the attend_event method like so:
def attend_event(event)
self.attended_events << event
p self.attended_events # random puts statement
end
It gets inserted in the correct order.
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 1, name: "dummy-event", user_id: 1>, #<Event id: 2, name: "dummy-event", user_id: 1>
What am I not getting here?
Unless you specify an order on the association, associations are unordered when they are retrieved from the database (the generated sql won't have an order clause so the database is free to return things in whatever order it wants)
You can specify an order by doing (rails 4.x upwards)
has_and_belongs_to_many :attended_events, scope: -> {order("something")}
or, on earlier versions
has_and_belongs_to_many :attended_events, :order => "something"
When you've just inserted the object you may see a different object - here you are probably seeing the loaded version of the association, which is just an array (wrapped by the proxy)

How to paginate an eager loaded association model

I have the following code :
Post.where("user_id IN [1, 2, 3, 4, 5, 6]").includes(:authors, :comments).paginate(page: params[:page], per_page: 30)
what i want here is to eager load just 8 comments per post using will_paginate, is this possible ? and how ?
Not a tested answer
I don't see that possible from there, but:
Comment.joins(:posts).includes(:posts).where(posts: { user_id: [1,2,3,4,5,6] })
I am not sure if joins and includes can be called together.
This would give you a relation for comments you can continue working on, and you will have eager loaded posts:
#comments = Comment.joins(:post).includes(:post).where(posts: { user_id: [1,2,3,4,5,6] })
#comments.paginate(...)
If you want to get the posts from #comments I would do this:
class Comment < ActiveRecord::Base
def self.post_ids
all.map(&:post_id)
end
def self.posts
Post.where(id: post_ids)
end
end
Then use and paginate it:
#posts = #comments.posts.paginate(...)

Manipulating ActiveRecords using only a few attributes with Ruby on Rails

For caching matters, I'm caching an array of the attributes of the objects I need:
friends = [{:id => 4, :name => "Kevin"}, {:id => 12, :name => "Martin"}, …]
Is it possible to have a list of Users using this array, so that I can use Ruby methods? For instance, I usually get a list of non-friends with this:
non_friends = User.all - current_user.friends
Here, current_user.friends would be replaced by the cached array, only with the cached attributes:
friends = [
#<User id: 4, name: "Kevin", created_at: nil, updated_at: nil, email: nil>,
#<User id: 12, name: "Martin", created_at: nil, updated_at: nil, email: nil>,
…
]
Is it possible? Is it a good approach to caching? (a big list of ActiveRecords doesn't fit into a 1MB Memcache chunk.)
Thank you,
Kevin
edit: The idea behind this is to use a sorted/processed list of 2000 ActiveRecords around which my app heavily uses, but since it doesn't fit into a Memcache chunk, I'm trying to cache the interesting attributes only as an array. Now, how can I use this array like it was an ActiveRecord array?
Well, you can just cache the User IDs and then exclude these IDs in your finder conditions. In your example, assuming you have a friends array of hashes containing ids and names:
friend_ids = friends.map{ |f| f[:id] }
if friend_ids.empty?
non_friends = User.all
else
non_friends = User.all(:conditions => ['id NOT IN (?)', current_user.friend_ids])
end

Resources