Rails 3 Eager Loading with conditions - how to access eager loaded data? - ruby-on-rails

I have a lot of cases in my app where a user has no more than one object (say, a "Description") within its association to another object (a "Group").
For example:
class User < ActiveRecord::Base
has_many :descriptions
has_many :groups
class Group < ActiveRecord::Base
has_many :users
has_many :descriptions
class Description < ActiveRecord::Base
belongs_to :user
belongs_to :group
If I wanted to render all the users in certain group and include their relevant descriptions, I could do something this:
#users model
def description_for(group_id)
descriptions.find_by_group_id(group_id)
end
#view
#group.users.each do |user|
user.name
user.description_for(#group.id).content
But this generates a huge number of Description queries. I've tried using joins:
#controller
#group = Group.find(params[:id], :joins => [{:users => :descriptions}], :conditions => ["descriptions.group_id = ?", params[:id]])
But since I'm still calling user.description_for(#group.id) it doesn't help with the page loading.
UPDATE: Sample generated SQL
Rendered users/_title.html.haml (1.6ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 7 LIMIT 1
CACHE (0.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 7 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 7 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 51 LIMIT 1
CACHE (0.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 51 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 51 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.8ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5 LIMIT 1
CACHE (0.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.1ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 5 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 5 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 37 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 52 LIMIT 1
CACHE (0.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = 28 LIMIT 1
Description Load (0.2ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 52 AND "descriptions"."group_id" = 28 LIMIT 1
CACHE (0.0ms) SELECT "descriptions".* FROM "descriptions" WHERE "descriptions"."target_id" = 52 AND "descriptions"."group_id" = 28 LIMIT 1
Rendered users/_title.html.haml (1.7ms)

Right, I think that actually you don't need the joins clause in rails 3. If you use include and where, Arel will do the hard work for you.
I've tested this (albeit using a different set of models (and attributes) than yours) using models with the same underlying arrangement of associations, and I think this should work:
in models/user.rb:
scope :with_group_and descriptions, lambda { |group_id| includes(:groups, :descriptions).where(:groups => { :id => group_id }, :descriptions => { :group_id => group_id }) }
Then in your controller you call:
#users = User.with_group_and_descriptions(params[:id])
Finally in the view you can then do:
#users.each do |user|
user.name
user.descriptions.each do |desc|
desc.content
# or
#users.each do |user|
user.name
user.descriptions[0].content
If I've gotten my thinking right then this should only make 2 db calls. One to get a list of user_ids and the second to get the user, group and description data, and even though you're calling a user object's descriptions method, which would ordinarily have all the descriptions in (not just the ones for a particular group), because you've already populated the association rails won't go off an grab all the associations again when you call user.descriptions, instead it'll just list the ones you've pulled from the DB using the descriptions.group_id where clause. Calling user.descriptions(true) however will force a reload of the descriptions leading to it returning an array of all the description associations for a user.

Take a look at include--it specifies an association that should be eager-loaded.
Railscasts #181: Include vs Joins (or the ASCIIcast version)
Ruby on Rails Guides - see section 4.1.2.7

Related

will_paginate deletes posts in a group when posts are paginated

I'm using will_paginate to paginate posts within a group.
A group might have many posts and I don't want to show all posts at once.
For some strange reason I noticed that a lot of posts are deleted from the database when I do #group.posts = paginated_posts as displayed in * groups_controller*.
I'm not doing save on #group, why are the posts deleted?
Tests first
69 it "Pagination – get one group and its posts" do
70 10.times { Fabricate(:post, group: #group, user: #user) }
71 puts "POST COUNT BEFORE #{#group.posts.count}"
72 # byebug
73 get group_path(#group, posts_per_page: 3)
74 puts "POST COUNT AFTER #{#group.posts.count}"
75 expect(response).to have_http_status(200)
76 expect(#group).to be_present
77 end
Output from the test
Groups
GET /group/:group_id?posts_per_page=10&posts_page=2
POST COUNT BEFORE 12
POST COUNT AFTER 3
groups_controller.rb
9 def show
10 paginated_posts = #group.posts.paginate(
11 page: params[:posts_page],
12 per_page: params[:posts_per_page] || 100000,
13 )
14 #group.posts = paginated_posts
15 render json: #group
16 end
The log
(0.3ms) RELEASE SAVEPOINT active_record_1
Started GET "/groups/33?posts_per_page=3" for 127.0.0.1 at 2018-09-18 12:52:13 +0000
Processing by GroupsController#show as HTML
Parameters: {"posts_per_page"=>"3", "id"=>"33"}
User Load (2.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", "78913bd2-4c72-4c73-ba75-421e154c830b"], ["LIMIT", 1]]
Group Load (1.4ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = $1 LIMIT $2 [["id", 33], ["LIMIT", 1]]
Post Load (2.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."group_id" = $1 LIMIT $2 OFFSET $3 [["group_id", 33], ["LIMIT", 3], ["OFFSET", 0]]
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."group_id" = $1 [["group_id", 33]]
(0.4ms) SAVEPOINT active_record_1
Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 156]]
PostImage Load (0.3ms) SELECT "post_images".* FROM "post_images" WHERE "post_images"."post_id" = $1 [["post_id", 156]]
Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 156]]
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 157]]
PostImage Load (0.3ms) SELECT "post_images".* FROM "post_images" WHERE "post_images"."post_id" = $1 [["post_id", 157]]
Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 157]]
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 158]]
#group.posts = ... doesn't work the way you think it does. If you have a one-to-many relationship and you assign an array of associated records to the existing group object, the records set and remove the group.id foreign key immediately. No save is required.
So when you do
#group.posts = paginated_posts
The paginated posts are assigned to the association and all other records in the association are removed from the association. Instantly. They're not necessarily deleted, but they no longer belong to #post
You may want to change your #to_json ... perhaps add
attr_accessor :page_of_posts
And then in your controller
#group.page_of_posts = paginated_posts
And ensure page_of_posts is what's rendered in the json. Others may suggest a better way.

Active Model Serializer caching of calculated field

I'm trying to to cache a collection of items, this is my AMS:
class ApplicationCategorySerializer < ActiveModel::Serializer
cache({})
cache key: 'application_category', expires_in: 3.days
attributes :id
attributes :name
attributes :active
attributes :group
attributes :num_of_protocols
belongs_to :group do |serializer|
serializer.attributes[:group][:name]
end
def num_of_protocols
ApplicationCategory.find(object.id).protocol_categories.count
end
end
My problem is that without the :num_of_protocols part it takes 10 times faster to generate the response,
when I look at the log I can see:
Started GET "/application_categories.json" for 127.0.0.1 at 2016-08-04 19:12:27 +0300
Processing by ApplicationCategoriesController#index as JSON
ApplicationCategory Load (3.0ms) SELECT "application_categories".* FROM "application_categories"
[active_model_serializers] Group Load (1.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = ? LIMIT 1 [["id", 2066]]
[active_model_serializers] ApplicationCategory Load (0.0ms) SELECT "application_categories".* FROM "application_categories" WHERE "application_categories"."id" = ? LIMIT 1 [["id", 1]]
[active_model_serializers] (0.0ms) SELECT COUNT(*) FROM "protocol_categories" INNER JOIN "application_categories_protocol_categories" ON "protocol_categories"."id" = "application_categories_protocol_categories"."protocol_category_id" WHERE "application_categories_protocol_categories"."application_category_id" = ? [["application_category_id", 1]]
[active_model_serializers] CACHE (0.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = ? LIMIT 1 [["id", 2066]]
[active_model_serializers] ApplicationCategory Load (0.0ms) SELECT "application_categories".* FROM "application_categories" WHERE "application_categories"."id" = ? LIMIT 1 [["id", 2]]
[active_model_serializers] (0.0ms) SELECT COUNT(*) FROM "protocol_categories" INNER JOIN "application_categories_protocol_categories" ON "protocol_categories"."id" = "application_categories_protocol_categories"."protocol_category_id" WHERE "application_categories_protocol_categories"."application_category_id" = ? [["application_category_id", 2]]
[active_model_serializers] CACHE (0.0ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = ? LIMIT 1 [["id", 2066]]
...................... [many many more lines like those...]
[active_model_serializers] CACHE (0.0ms) SELECT "application_categories".* FROM "application_categories" WHERE "application_categories"."id" = ? LIMIT 1 [["id", 608]]
[active_model_serializers] CACHE (0.0ms) SELECT COUNT(*) FROM "protocol_categories" INNER JOIN "application_categories_protocol_categories" ON "protocol_categories"."id" = "application_categories_protocol_categories"."protocol_category_id" WHERE "application_categories_protocol_categories"."application_category_id" = ? [["application_category_id", 608]]
[active_model_serializers] CACHE (0.0ms) SELECT "application_categories".* FROM "application_categories" WHERE "application_categories"."id" = ? LIMIT 1 [["id", 609]]
[active_model_serializers] CACHE (0.0ms) SELECT COUNT(*) FROM "protocol_categories" INNER JOIN "application_categories_protocol_categories" ON "protocol_categories"."id" = "application_categories_protocol_categories"."protocol_category_id" WHERE "application_categories_protocol_categories"."application_category_id" = ? [["application_category_id", 609]]
[active_model_serializers] CACHE (0.0ms) SELECT "application_categories".* FROM "application_categories" WHERE "application_categories"."id" = ? LIMIT 1 [["id", 610]]
[active_model_serializers] CACHE (0.0ms) SELECT COUNT(*) FROM "protocol_categories" INNER JOIN "application_categories_protocol_categories" ON "protocol_categories"."id" = "application_categories_protocol_categories"."protocol_category_id" WHERE "application_categories_protocol_categories"."application_category_id" = ? [["application_category_id", 610]]
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (1521.15ms)
how can I get better results with caching? - when I comment out the cache lines I get aprox. same times as with them, when I remove the :num_of_protocols I get same time with & without cache.
what am I configuring wrong?
Can you do this?
def num_of_protocols
object.protocol_categories.count
end
And, it's been a while for serializers for me, but I thought you were able to do this...
def num_of_protocols
protocol_categories.count
end
And this is better ruby (in the case protocal_categories is already loaded as an array)
def num_of_protocols
protocol_categories.size
end
And, if all that works or not, make sure you include protocol_categories and groups when you instantiate #application_category before you render to json. You're doing too many queries.

Rails 4 - Why does using includes() not make a join between Pins and Replies?

I'm a Rails and Ruby newcomer. I want to optimise SQL queries where I can and I was reading about using includes() to make Rails aware, that I want to eager load and join two tables.
In my show action on the pin controller:
def show
#pin = Pin.includes(:replies, :user).where(id: params[:id]).first
end
If I check the log on the queries, I see the following:
Started GET "/pin/1703704382" for 127.0.0.1 at 2014-06-12 15:30:18 +0100
Processing by PinsController#show as HTML
Parameters: {"id"=>"1703704382"}
Pin Load (0.2ms) SELECT `pins`.* FROM `pins` WHERE `pins`.`id` = 145 ORDER BY `pins`.`id` ASC LIMIT 1
Reply Load (0.1ms) SELECT `replies`.* FROM `replies` WHERE `replies`.`pin_id` IN (145)
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (22)
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 22 LIMIT 1
Profile Load (0.1ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 22 ORDER BY `profiles`.`id` ASC LIMIT 1
CACHE (0.0ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 22 ORDER BY `profiles`.`id` ASC LIMIT 1 [["user_id", 22]]
Type Load (0.1ms) SELECT `types`.* FROM `types` WHERE `types`.`id` = 1 ORDER BY `types`.`id` ASC LIMIT 1
Skill Load (0.1ms) SELECT `skills`.* FROM `skills` WHERE `skills`.`id` = 3 ORDER BY `skills`.`id` ASC LIMIT 1
Instrument Load (0.2ms) SELECT `instruments`.* FROM `instruments` WHERE `instruments`.`id` = 6 ORDER BY `instruments`.`id` ASC LIMIT 1
Genre Load (0.1ms) SELECT `genres`.* FROM `genres` INNER JOIN `genre_pins` ON `genres`.`id` = `genre_pins`.`genre_id` WHERE `genre_pins`.`pin_id` = 145
Bookmark Load (0.1ms) SELECT `bookmarks`.* FROM `bookmarks` WHERE `bookmarks`.`pin_id` = 145
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 22 ORDER BY `users`.`id` ASC LIMIT 1
(0.2ms) SELECT COUNT(*) FROM `bookmarks` WHERE `bookmarks`.`pin_id` = 145
(0.1ms) SELECT COUNT(*) FROM `replies` WHERE `replies`.`pin_id` = 145
Rendered partials/_pin.html.erb (14.3ms)
Pin Load (0.1ms) SELECT `pins`.* FROM `pins` WHERE `pins`.`id` = 145 ORDER BY `pins`.`id` ASC LIMIT 1
CACHE (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 22 ORDER BY `users`.`id` ASC LIMIT 1 [["id", 22]]
CACHE (0.0ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 22 ORDER BY `profiles`.`id` ASC LIMIT 1 [["user_id", 22]]
Rendered replies/_reply.html.erb (4.0ms)
Rendered replies/_form.html.erb (1.5ms)
Rendered pins/show.html.erb within layouts/application (21.7ms)
Rendered partials/_meta.html.erb (0.1ms)
Rendered partials/_top.html.erb (1.3ms)
Rendered partials/_tags.html.erb (0.1ms)
Rendered partials/_search.html.erb (1.0ms)
Completed 200 OK in 33ms (Views: 27.2ms | ActiveRecord: 2.0ms | Solr: 0.0ms)
It seems like it is running separate queries to get pins, replies and the user. How can I join these into one query? Surely this could be better optimised.
Thanks for your advice and patience!
It seems like it is running separate queries to get pins, replies and the user. How can I join these into one query? Surely this could be better optimised.
This is not automatically true. Hydration (the fact of creating nested records, etc, since you don't get in a hierarchical structure from your DB) can be very costly with many relations. It's actually often times better for your performance to use only a very simple query to fetch every record of one table.
If you still want to join (with shallow relations, it's probably better), you can use .joins

accepts_nested_attributes_for runs before controller method

I'm having a baffling problem. I have a LineItem model that accepts nested attributes for a Trackable and a Product. I am overriding the default trackable_attributes= and product_attributes= methods to allow me to find or initialize records. For example:
def trackable_attributes=(attributes)
_trackable = Project.find_or_initialize_by_id(attributes.delete("id"))
_trackable.attributes = attributes
self.trackable = _trackable
end
When I post to the LineItemController#create, even when there's only a logging statement in it, it runs all of the posted parameters as if I'm trying to build/create a new line item. I don't have any before_filters that would be doing this on the #create method.
def create
logger.info("LineItemsController#create")
end
Webserver logs:
Started POST "/line_items" for 127.0.0.1 at 2013-04-10 14:02:13 -0700
Processing by LineItemsController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MY_SECRET_TOKEN", "line_item"=>{"trackable_type"=>"Project", "tax_rate"=>"0", "price"=>"0.00", "provided_on_formatted"=>"Apr 10", "trackable_attributes"=>{"company_attributes"=>{"id"=>"87", "name"=>"Handmade Design"}, "id"=>"45", "name"=>"MEdia Blitz"}, "product_attributes"=>{"id"=>"75"}, "name"=>"Design", "notes"=>"Yellow", "quantity"=>"0.00", "unit_price"=>"205.0"}, "commit"=>"+"}
Person Load (0.3ms) SELECT "people".* FROM "people" WHERE "people"."id" = 73 LIMIT 1
(0.1ms) begin transaction
(0.4ms) UPDATE "people" SET "last_request_at" = '2013-04-10 21:02:13.861629', "perishable_token" = 'kzsB5dnCOHfykO1XMMi9', "updated_at" = '2013-04-10 21:02:13.863622' WHERE "people"."id" = 73
[paperclip] Saving attachments.
(1.2ms) commit transaction
Account Load (0.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = 369 LIMIT 1
Person Load (0.3ms) SELECT "people".* FROM "people" WHERE "people"."account_id" = 369 ORDER BY id LIMIT 1
{"company_attributes"=>{"id"=>"87", "name"=>"Handmade Design"}, "id"=>"45", "name"=>"MEdia Blitz"}
Project Load (0.2ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = 45 LIMIT 1
Company Load (0.2ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = 87 LIMIT 1
trackable_attributes=(attributes)
trackable assigned
{"id"=>"75"}
Product Load (0.2ms) SELECT "products".* FROM "products" WHERE "products"."id" = 75 LIMIT 1
product_attributes=(attributes)
product assigned
LineItemsController#create
Notice that I have many logging statements scattered through my custom accepts_nested_attributes_for setters that are being called before the #create method even begins.
Is this normal? It seems to be causing problems with when I want to instantiate a new line_item the normal way in my LineItemsController#create method.
I'd really appreciate any help as I'm lost on where to start.

Previous Button Returns to First for Some Reason in Rails 3 Using Nested Routes

I have a next button that is working great, but previous button just does not want to play nice. Funny thing is that I've tried to build this a few different ways referencing methods on SO and elsewhere, but previous always goes to the earliest record...
In my submission model:
def previous
self.class.first(:conditions => ["created_at < ?", created_at], :order => "created_at asc")
end
def next
self.class.first(:conditions => ["created_at > ?", created_at], :order => "created_at asc")
end
And in my view:
<% if #submission.previous %>
<%= link_to "previous", contest_submission_path(#contest, #submission.previous),
:class => 'pull-left btn btn-large' %>
<% end %>
<% if #submission.next %>
<%= link_to "next", contest_submission_path(#contest, #submission.next),
:class => 'pull-right btn btn-large' %>
<% end %>
Like I said, next works great, but previous returns to the earliest record. I'm nearing wits end with this one. Anybody have an answer?
EDITED
Logs:
Started GET "/contests/1/submissions/1" for 127.0.0.1 at 2012-11-30 08:08:52 -0800
Processing by SubmissionsController#show as HTML
Parameters: {"contest_id"=>"1", "id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 ORDER BY users.created_at DESC LIMIT 1
Contest Load (0.3ms) SELECT "contests".* FROM "contests" WHERE "contests"."id" = $1 LIMIT 1 [["id", "1"]]
Submission Load (0.2ms) SELECT "submissions".* FROM "submissions" WHERE "submissions"."id" = $1 ORDER BY submissions.created_at ASC LIMIT 1 [["id", "1"]]
CACHE (0.0ms) SELECT "submissions".* FROM "submissions" WHERE "submissions"."id" = $1 ORDER BY submissions.created_at ASC LIMIT 1 [["id", "1"]]
Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."commentable_id" = 1 AND "comments"."commentable_type" = 'Submission' ORDER BY comments.created_at DESC, created_at
Submission Load (0.3ms) SELECT "submissions".* FROM "submissions" WHERE (created_at < '2012-10-08 14:32:40.590930') ORDER BY submissions.created_at ASC, created_at asc LIMIT 1
Submission Load (0.2ms) SELECT "submissions".* FROM "submissions" WHERE (created_at > '2012-10-08 14:32:40.590930') ORDER BY submissions.created_at ASC, created_at asc LIMIT 1
CACHE (0.0ms) SELECT "submissions".* FROM "submissions" WHERE (created_at > '2012-10-08 14:32:40.590930') ORDER BY submissions.created_at ASC, created_at asc LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 ORDER BY users.created_at DESC LIMIT 1
Image Load (0.2ms) SELECT "images".* FROM "images" WHERE "images"."parent_id" = 2 AND "images"."parent_type" = 'User' LIMIT 1
Rendered submissions/_follow_unfollow.html.erb (0.1ms)
Image Load (0.4ms) SELECT "images".* FROM "images" WHERE "images"."parent_id" = 1 AND "images"."parent_type" = 'Submission' LIMIT 1
Rendered submissions/_hide_comments_form.html.erb (1.9ms)
Rendered comments/_form.html.erb (2.5ms)
Rendered comments/_comment.html.erb (3.5ms)
Rendered submissions/show.html.erb within layouts/application (21.7ms)
Rendered layouts/_stylesheets.html.erb (4.3ms)
CACHE (0.0ms) SELECT "images".* FROM "images" WHERE "images"."parent_id" = 2 AND "images"."parent_type" = 'User' LIMIT 1
Role Load (0.5ms) SELECT "roles".* FROM "roles" INNER JOIN "assignments" ON "roles"."id" = "assignments"."role_id" WHERE "assignments"."user_id" = 2
Rendered layouts/_header.html.erb (4.6ms)
Rendered layouts/_footer.html.erb (0.7ms)
Completed 200 OK in 105ms (Views: 97.8ms | ActiveRecord: 3.0ms)
Log #2
Started GET "/contests/1/submissions/%23%3CActiveRecord::Relation:0x007fc3b5e50af8%3E" for 127.0.0.1 at 2012-11-30 08:43:15 -0800
Processing by SubmissionsController#show as HTML
Parameters: {"contest_id"=>"1", "id"=>"#<ActiveRecord::Relation:0x007fc3b5e50af8>"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 ORDER BY users.created_at DESC LIMIT 1
Contest Load (0.3ms) SELECT "contests".* FROM "contests" WHERE "contests"."id" = $1 LIMIT 1 [["id", "1"]]
Submission Load (0.3ms) SELECT "submissions".* FROM "submissions" WHERE "submissions"."id" = $1 LIMIT 1 [["id", "#<ActiveRecord::Relation:0x007fc3b5e50af8>"]]
Completed 500 Internal Server Error in 4ms
ActiveRecord::RecordNotFound (Couldn't find Submission with id=# <ActiveRecord::Relation:0x007fc3b5e50af8>):
app/controllers/submissions_controller.rb:89:in `find_submission'
Final Solution:
In submission.rb
default_scope order: 'submissions.created_at DESC'
def previous_sub
self.class.where("created_at > ?", created_at).reorder("created_at asc").first
end
def next_sub
self.class.where("created_at < ?", created_at).reorder("created_at desc").first
end
In view (show):
<% if #submission.previous_sub %>
<%= link_to "previous", contest_submission_path(#contest, #submission.previous_sub),
:class => 'pull-left btn btn-large' %>
<% end %>
<% if #submission.next_sub %>
<%= link_to "next", contest_submission_path(#contest, #submission.next_sub),
:class => 'pull-right btn btn-large' %>
<% end %>
I ended up exchanging the names of the methods, so that the show record order would match the index, which is scoped in DESC order or most recent to earliest.
Your code is correct, but your log shows
SELECT "submissions".* FROM "submissions"
WHERE (created_at < '2012-10-08 14:32:40.590930')
ORDER BY submissions.created_at ASC, created_at asc LIMIT 1
If this is a query that searches for previous, this is where it fails (because it orders the previous dates in ascending order, and takes the first - the earliest). Could it be you forgot to restart the app after making changes (though it should be reloaded automatically in dev mode)?
Or it is possible you have default_scope or some other scope that breaks the conditions. I wonder why there is submissions.created_at ASC, created_at asc - twice? Probably submissions.created_at ASC is a default scope.
I'd suggest you to use new AR query syntax, and perhaps reorder to exclude other ordering scopes:
self.class.where("created_at < ?", created_at).reorder("created_at desc").first

Resources