I have the following code in my practices controller index method:
#practices = #activity.practices.order('created_at DESC').limit(20)
Unfortunately, the limit call is not working, and #practices is returning >20 objects. I'm sure this is a simple thing to fix, but I'm not sure what I'm doing wrong. I seem to be using this method in the way prescribed in the docs..
I have tried placing the call to limit before the order call, with the same result.
UPDATE
SQL LOG:
Practice Load (1.6ms) SELECT "practices".* FROM "practices" WHERE ("practices".activity_id = 9) ORDER BY practiced_on DESC, created_at DESC LIMIT 20
However, further down the log, I found this:
Rendered practices/_practice.html.erb (216.9ms)
Activity Load (0.6ms) SELECT "activities".* FROM "activities" WHERE ("activities".user_id = 1)
Practice Load (0.8ms) SELECT "practices".* FROM "practices" WHERE ("practices".activity_id = 8) ORDER BY practiced_on DESC
CACHE (0.0ms) SELECT "practices".* FROM "practices" WHERE ("practices".activity_id = 9) ORDER BY practiced_on DESC
Which leads me to think that the partial is not accepting the correct collection. The partial is called thus:
<%= render #activity.practices %>
Any advice?
TIA
It looks like you are returning your limited collection correctly and storing it in the #practices instance variable.
However, you are rendering your partial with #activity.practices, which will trigger a lazy load of the entire unlimited practices collection, because that collection has not yet been loaded on the #activity object.
The answer
<%= render #practices %> should work.
Bonus round
Finally, if you want something a little more 'object-oriented' you could put your 'limited' query into a scope called 'recent_practices' so you could call:
<%= render :partial => 'practices', :object => #activity.recent_practices %>
The above example would use a local variable called 'recent_practices' inside the partial, rather than an instance variable.
More about ActiveRecord scopes here.
Related
I have a simple model:
class Reply < ActiveRecord::Base
attr_accessible :body
belongs_to :post
end
In my controller, I have a simple update method:
def update
#reply = Reply.find(params[:id])
if #reply.update_attributes!(params[:reply])
render :js => "alert('I am trying to update!')"
else
render :js => "alert('<%= #reply.errors %>')"
end
end
This doesn't throw an error, but neither does it actually update the reply. Instead, I get the "I am trying to update!" message, like everything worked. But when I reload the page and look at the reply, it has the same text. It hasn't actually been updated. If I replace update_attributes with:
#reply.update_column(:body, params[:reply][:body])
It works fine. If I use:
#reply.update_attribute(:body, params[:reply][:body])
It once again doesn't work. Any idea what's going?
In my log, I have this:
Started PUT "/posts/2/replies/20" for 127.0.0.1 at 2013-01-19 10:39:57 -0600
Processing by RepliesController#update as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Xot7E+ldXiBm0hVvw5XUP/U5guJU2g8e4QaLbDVGzDE=", "reply"=>{"body"=>"Updated text."}, "commit"=>"Submit Revision", "post_id"=>"2", "id"=>"20"
[1m[35mUser Load (1.0ms)[0m SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
[1m[36mReply Load (0.0ms)[0m [1mSELECT `replies`.* FROM `replies` WHERE `replies`.`id` = 20 LIMIT 1[0m
[1m[35m (1.0ms)[0m BEGIN
[1m[36mPost Load (0.0ms)[0m [1mSELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 2 LIMIT 1[0m
[1m[35m (0.0ms)[0m COMMIT
Rendered replies/_reply_content.html.erb (502.0ms)
Rendered replies/update.js.erb (505.0ms)
Completed 200 OK in 849ms (Views: 484.0ms | ActiveRecord: 94.0ms)
The three methods you are using do different things:
update_attributes tries to validate the record, calls callbacks and saves;
update_attribute doesn't validate the record, calls callbacks and saves;
update_column doesn't validate the record, doesn't call callbacks, doesn't call save method, though it does update record in the database.
If the only method that "works" is update_column my guess is that you have a callback somewhere that is throwing an error. Try to check your log/development.log file to see what's going on.
You can also use update_attributes!. This variant will throw an error, so it may give you information on why your model isn't saving.
You should use update_attributes and avoid the two other methods unless you know exactly what you are doing. If you add validations and callbacks later to your model, using update_attribute and update_column can lead to nasty behaviour that is really difficult to debug.
You can check this link for more info on that.
I had this same issue, but with Rails 4. The issue happens when you have params[] in update_attribute. In Rails 4 with strong parameters
#reply.update_attributes(params[reply_params])
should be
#reply.update_attributes(reply_params)
I'm not to familiar with Rails 3 but this should be the issue:
#reply.update_attributes(params[:reply])
should be
#reply.update_attributes(:reply)
A gut guess would be to say that you have a mass assignment problem and should add your attributes in your model like this
attr_accessible: :your_attribute, :your_attribute2
My action populates an instance variable #websites - which it gets from the database.
#websites = Website.all
I can spin through the #websites and see there is only one call to the database. Happy Days.
<% #websites.each do |website| %>
...
<% end %>
This results in:
Website Load (0.1ms) SELECT "websites".* FROM "websites"
But if I wrap it with a present? like so:
<% if #websites.present? %>
<% #websites.each do |website| %>
<% end %>
<% else %>
Now I get two database calls
(0.2ms) SELECT COUNT(*) FROM "websites"
Website Load (0.1ms) SELECT "websites".* FROM "websites"
Why are there two database calls being made? And how do I stop the second call being made.
To me the #websites variable has all of the websites in memory. Surely rails would just count the items in memory rather than doing a new SELECT on the database?
This method call,
#websites = Website.all
does not load the records immediately into memory. Its just an active record relation. The first operation that you do with this relation is calling the present? and to satisfy this request, Rails need not load all the records into memory. So ActiveRelation just optimizes the relation and does a count query to get you the results. Now only when you access the #websites using the each method the records are loaded.
If you want to avoid that extra query, you can convert the Relation to an array but doing,
#websites = Website.all.to_a
This loads the records and renders an Array instead of ActiveRelation. But the catch is Active relation cannot chain further queries with this relation. But I am assuming that isnt necessary in your situation.
Following.rb
belongs_to :show
def cached_show
Rails.cache.fetch([self, :show]) do
show
end
end
View:
<% #recently_favorited.each do |following| %>
<li>
<%= link_to "#{following.cached_show.name}", show_path(:permalink => following.cached_show.permalink) %> <span><%= "(#{pluralize(following.cached_show.followers, "follower")})" %></span>
</li>
<% end %>
Result in the console:
Cache read: followings/632770-20120929132253/show
Cache generate: followings/632770-20120929132253/show
Show Load (0.7ms) SELECT `shows`.* FROM `shows`WHERE `shows`.`id` = 617 LIMIT 1
Cache write: followings/632770-20120929132253/show
Cache read: followings/632770-20120929132253/show
Cache fetch_hit: followings/632770-20120929132253/show
Cache read: followings/632770-20120929132253/show
Cache fetch_hit: followings/632770-20120929132253/show
Question:
Is this even a "correct" implementation of fetching/caching an association?
And what about performance?
In some views (as in the example) it will hit the cache 3 times per loop. In my case I'm looping 10 items in the footer, so it will make 30 hits on every request. Is this fine, or will a single n+1 query per loop be better?
Advise and general best practices appreciated :)
Creating a distinct method to hit the cache vs getting it fresh is not common from what I can tell.
Most of the time, you'd just call a method that asks the cache all the time, since if you include an object in the cache-key, the updated_at field is used to build the key.
For your example now, the weird part is that you don't actually do anything with the Following model apart accessing its association. Therefore, you should query directly on the Show model :
#recently_favorited_shows = Show.joins(:followings).order("followings.created_at DESC").uniq
Then in your view, loop on the shows. Only one query, no n+1
If you expect thousands of hits then, I'd just suggest to cache the result of #recently_favorited_shows and expire it every X minutes :
#recently_favorited_shows = cache_store.fetch('recently_favorited_shows', expires_in: 5.minutes){Show.joins(:followings).order("followings.created_at DESC").uniq}
On another note, here's a good write-up on cache usage on the view side if you want to do it some time: http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
No joins solution
Edit : now, if you have gazillions of rows in followings table, here's what I'd do :
Create a field last_followed_at on the shows table, with an index on it
In Following.rb : belongs_to :show, touch: :last_followed_at. This way, as soon as you add a new entry in Following, it'll update the field on the shows table
Then, to get the latest followed shows, do :
#shows = Show.order("last_followed_at DESC").limit(10) # Fast query of course
This doesn't answer my question, but it solves my problem. Here's how I'll do it instead:
#shows = Rails.cache.fetch("recently_favorited_shows", expires_in: 1.minutes) do
Show.find(Following.order("created_at DESC").limit(10).collect(&:show_id))
end
The queries are pretty fast (~0.8ms each says the IRB console)
Lets say you have a post with comments on the same page, and you render a form for capturing a new comment also on the same page as you are displaying the post/comments. A post has_many comments. Code as follows:
class PostsController < ApplicationController
...
def show
#post = Post.find(:params[id])
#comment = Post.comments.new
end
...
end
Now when you call <%= #post.comments.count %> in your views it gives the number of comments that have been saved, but if you call <%= render #post.comments %> it returns all the saved comments PLUS the newly created (but not yet saved and therefore still invalid) comment. Why is this? This has really taken me time to find this and I can't imagine that this would be useful, why not just render all the valid database records?
Has anyone else ran into this? Easy to fix but puzzling..
Well, #post.comments.count actually does a database query and can therefore only return the number of saved records. (Use #post.comments.size or .length) for the number of objects in your collection.
The render call, AFAIK, only loops over the objects in the collection.
The thing to know here is the difference between when you do actual queries with the association, and when active record is using the cached objects. It is perhaps easy to assume that the comments in #post.comments is just an Array. It actually is a fancy proxy object that, depending on method called and state of the cached collection, acts like an Array or as an interface to the Model's query methods.
So after searching for a tagging gem for my rails app I found the amazing acts-as-taggable gem. Installing it and playing around I discovered that it keeps all the Tags inside a tag db which just holds the Tag.name without the context, instead the context is held in the :through relationship db ( taggings ). For most purposes I can see this being perfect. Except with my app I want to be able to offer the user the ability to tag based on an pre-existing tags ( eg not allow them to create their own ) and acts-as-taggable doesn't have the ability to search all tags within one context built in ( eg, if I were to present an auto-completion of the tag db I would have all the tags in my app included which is'nt what I want )
The method below is what I just fleshed out to see if it would work ( which it does ) but I wondered if I was missing something with acts-as-taggable. I mean I can't see anywhere that offers this kind method?
<% ActsAsTaggableOn::Tagging.find_all_by_context("tags").each do |tagging| %>
<%= tagging.tag %>
<% end %>
If for instance acts-as-taggable doesn't do this, is this the best way to do this? It feels a little non performant, Would I be better doing a custom SQL query instead of routing through acts-as-taggable?
If it helps at all heres a tail of my log:
Started GET "/users" for 127.0.0.1 at 2011-01-04 14:46:20 +0000
Processing by UsersController#index as HTML
SQL (0.5ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
User Load (0.1ms) SELECT "users".* FROM "users"
ActsAsTaggableOn::Tagging Load (0.5ms) SELECT "taggings".* FROM "taggings" WHERE ("taggings"."context" = 'languages')
ActsAsTaggableOn::Tag Load (0.1ms) SELECT "tags".* FROM "tags" WHERE ("tags"."id" = 2) LIMIT 1
Rendered users/index.html.erb within layouts/application (10.4ms)
You could also use a statement like the following:
# Returns all the tags for the specified model/context with a count >= 1
#tags = YourModel.tag_counts_on(**context**)
Add limit and order:
# Get the top 5 tags by count
#tags = YourModel.tag_counts_on(**context**, :limit => 5, :order => "count desc")
Access the counts with the count attribute of the tags returned from tag_counts_on
tag.count
I believe there is the way:
User.tag_counts_on(:tags)
=> [#<ActsAsTaggableOn::Tag id: 1, name: "foo">,
#<ActsAsTaggableOn::Tag id: 2, name: "bar">,
#<ActsAsTaggableOn::Tag id: 3, name: "sushi">,
#<ActsAsTaggableOn::Tag id: 4, name: "pizza">]