Rails 4.0.0.0 Russian Doll Caching with belongs_to model - ruby-on-rails-3

I have set up a cache in my model like
def self.latest(shop_id)
Inventory.where(:shop_id => shop_id).order(:updated_at).last
end
and in my view
<% cache ['inventories', Inventory.latest(session[:shop_id])] do %>
<% #inventories.each do |inventory| %>
<% cache ['entry', inventory] do %>
<li><%= link_to inventory.item_name, inventory %></li>
So, here I can have many shops, each with an inventory of stock items. Will the above cache work at all for different shops?
I think it's possible that even displaying the view in a different shop will break the cache. Or, any shop adding an inventory item will break the cache.
Can I use Russian Doll caching like this or do I need to use Inventory.all in my model?

Your idea is close, but you need to include the shop_id, the count, and the maximum updated_at of each shop's inventory into your cache key. Your outer cache needs to get busted when a shop's item gets deleted too, and that isn't covered under a max id or updated_at alone.
You can expand your custom cache key helper method to make this work. This allows you to create unique top level caches that only get busted when a member of that set gets added, updated or deleted. In effect, this gives a unique outer cache for each shop_id. Thus when one shop's inventory is changed, it doesn't affect another shop's cache.
Here is an example, based on ideas in the edge rails documentation:
module InventoriesHelper
def cache_key_for_inventories(shop_id)
count = Inventory.where(:shop_id => shop_id).count
max_updated_at = Inventory.where(:shop_id => shop_id).maximum(:updated_at).try(:utc).try(:to_s, :number)
"inventories/#{shop_id}-#{count}-#{max_updated_at}"
end
end
Then in your view:
<% cache(cache_key_for_inventories(session[:shop_id])) do %>
...
<% end %>

Related

optimize sql query rails

On posts index page I list all posts this way:
posts_controller.rb
def index
#posts = Post.includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>
35 posts per page
When I first load the page in dev env,
rack-mini-profiler shows this time: 1441.1 ms
after a few reloads: ~700 ms
Can I somehow decrease this time and number of sql requests?
Here're rmp images if it helps:
You could decrease the number of sql queries by:
including user as well as comments, since you seem to be using that when displaying the gravatar
changing post.comments.count to post.comments.size
While size, count, length are synonymous for arrays, for active record relations or associations they are not the same:
length loads the association (unless it is already loaded) and returns the length of the array
count does a select count(*) query whether the association is loaded or not
size uses length if the association is loaded and count if not.
In your case the comments association is loaded, but because you are using count, it's not actually used
Further, you don't actually seem to be using the comments collection for anything other than printing the number of records. If that's indeed the case, use counter_cache (4.1.2.3) instead of querying for the comments (the number of comments will be available in the parent record Post).
Also consider a client side alternative to time_ago_in_words. It will also help if you later decide to cache the entire section/page.
And finally retrieve only the fields you're going to use. In this case, I can imagine the Post contains a large amount of text for the content and it's not used anywhere (but still needs to be transmitted from the DB).
Adding an index on the reference column (comments in your case) might help.
add_index :posts, :comment_id

How can I create an array of objects from a list of associated checkboxes?

First of all, I've done a fair amount of looking around, and while questions get around answers, I have a problem I think is somewhat unique. I have a list of checkboxes generated with the following code:
<% for student in Student.find(:all) %>
<div>
<%= check_box_tag "user[student_ids][]", student.id, current_user.students.include (student) %>
<%= student.name %>
</div>
<% end %>
After clicking the 'update' button at the bottom, I need each of the checked boxes to be placed into an array. I then plan on iterating over the array and doing some work on each of the checked names. I am having a hard time, however, with the process of getting these names all into an array. I really am not sure which of the standard web actions this kind of work should be (i.e, post, get, etc.), so I don't know how to set up a route. Even if I could set up a route to a controller, how would I get the checked students into an array of Student objects?
Thanks ahead of time for your help!
The full answer to your question depends on a variety of things, for example, what you are trying to do with the submitted array, etc (which would determine whether POST, GET, PUT or DELETE should be used.) Without knowing more information with respect to your code base, if you throw the following code into a form_for in one of your controller's already restful routes, you should be able to see the array of checked names:
<%= current_user.students.include(student).each do |student| %>
<div>
<%= check_box_tag "student_names[]", student.name %> <%= label_tag student.name %>
</div>
<% end %>
Then, when the user hits submit, the params hash will show student_names = [].
And make sure your attributes are accessible as needed.
On a side note, check out Railscasts pro episode from last week. Pretty much exactly explains what you are trying to do. It's a subscription service, though.
I managed to solve my problem in a less-than-satisfying way. Here is the code I ended up using:
current_user.students.delete_all
if(params.has_key? :user)
params[:user][:student_ids].each do |i|
current_user.students<<(Student.find(i))
end
end
Because the number of students I'm managing is not ever larger than 100, this operation isn't as bad as it looks. I'm deleting all of the associations already present, and then cycling through all passed parameters. I then find the student object with the passed parameter id and add it to the current_user's User-Student join table.
I hope this helps someone down the line!

Does MongoID do a separate query for .count(true)?

I have a ruby on rails 3 project in which I query for a certain number of objects by using a .limit(3) . Then, in my view, I loop through these objects. After that, if there are 3 objects in the view, I display a "load more" button. Here is the view code:
<% #objects.each do |object| %>
<%= render object._type.pluralize.underscore + '/teaser', :object => object %>
<% end %>
<% if #objects.size(true) == 3 %>
#load more link here
<% end %>
The size(true) is passed a boolean to ensure that mongoID takes into account the .limit and .offset on my query (otherwise it returns the total number of objects that matched, regardless of the limit / offset). Here are the relevant development log lines:
MONGODB project_development['system.indexes'].insert([{:name=>"_public_id_1", :ns=>"project_development.objects", :key=>{"_public_id"=>1}, :unique=>true}])
MONGODB project_development['objects'].find({:deleted_at=>{"$exists"=>false}}).limit(3).sort([[:created_at, :desc]])
#some rendering of views
MONGODB project_development['system.indexes'].insert([{:name=>"_public_id_1", :ns=>"project_development.objects", :key=>{"_public_id"=>1}, :unique=>true}])
MONGODB project_development['$cmd'].find({"count"=>"objects", "query"=>{:deleted_at=>{"$exists"=>false}}, "limit"=>3, "fields"=>nil})
My question is: does MongoID do a separate query for my #objects.size(true)? I imagine the ['$cmd'] might indicate otherwise, but I'm not sure.
I don't think so, there was a pull request month ago to add aliases for :size, :length to :count to avoid re-running queries. You can check that.

How do I make a settings configuration page for the rails-settings gem?

I just discovered the rails-settings gem and now I need to make an admin page that lets me edit the setting values. How would I make a settings controller with an edit view that can change these dynamic app wide settings?
I haven't used this gem but it seems like it should be fairly straight forward. Since it uses a database backed model, you would simply create a controller as normal:
rails g controller Settings
From here you would define your index action to gather all your individual settings for display in the view:
def index
#settings = Settings.all
end
Then in the view you can setup a loop to display them:
<% #settings.each do |setting| %>
<%= setting.var %> = <%= setting.value %>
<% end %>
As far as editing ... this might be a bit tricky since by default rails would expect you to submit only one setting at a time to edit. You could do it this way but unless you implement the edit with ajax it might be tedious and non-intuitive.
Another way would be to set up your update method to accept all the individual settings at once, loop through and update each one with new values. It might look something like this:
// The /settings route would need to be setup manually since it is without an id (the default)
<%= form_tag("/settings", :method => "put") do %>
<% #settings.each do |setting| %>
<%= label_tag(setting.var, setting.var) %>
<%= text_field_tag(setting.var, :value => setting.value) %>
<% end %>
<%= submit_tag("Save Changes") %>
<% end %>
This should output all of the settings (given they have been assigned to the #settings variable) with the var name as the label and the current value as the text field value. Assuming that the routing is setup, when you submit this form the action that receives it should all the new settings in the params variable. Then you can do something like this in the action:
def update
params.each_pair do |setting, value|
eval("Settings.#{setting} = #{value}")
end
redirect_to settings_path, :notice => 'Settings updated' # Redirect to the settings index
end
This may not be the best way depending on how often you edit the settings and how many settings you have...but this is a possible solution.
I was looking for some suggestions for this and found another answer to this that is very simple and elegant, for anyone looking for this later. It just sets up dynamic accessors in your model, allowing your form to have settings fields just like your normal attributes. An example can be found in the original answer:
How to create a form for the rails-settings plugin

How can I make this Ruby on Rails page more efficient?

I'm building a site where users can track their collection of figures for Dungeons & Dragons (www.ddmdb.com). The models/relationships involved in this funcitonality are the following:
User:
id
login (username)
a bunch of other fields
Miniature:
id
name
number (# in the set, not count)
release_id (foreign key)
a bunch of other fields and foreign keys
Ownership:
id (is this really even needed?)
user_id
miniature_id
have_count
favorite (boolean)
The pertinent relationships I have set up are as follows:
User:
has_many :ownerships
has_many :miniatures, :through => :ownerships, :uniq => true, :conditions => "ownerships.have_count > 0"
has_many :favorites, :through => :ownerships, :source => :miniature, :uniq => true, :conditions => "ownerships.favorite = true"
Miniatures:
has_many :ownerships
has_many :owners, :through => :ownerships, :source => :user, :uniq => true, :conditions => "ownerships.have_count > 0"
Ownership:
belongs_to :user
belongs_to :miniature
I have a page where user's can both view and update their collection, as well as view other user's collections. It contains a list of all the miniatures on the site and a text box next to each where the user can enter how many of each miniature they have. This functionality also exists in sub-lists of miniatures (filtered by type, release, size, rarity, etc.)
When a user creates an account they have no entries in the ownership. When they use the collection page or sub-list of miniatures to update their collection, I create entries in the ownership table for only the miniatures on the submitting page. So if it's the full Collection list I update all minis (even if the count is 0) or if it's a sub-list, I only update those miniatures. So at any time a particular user I may have:
- no entries in ownership
- entries for some of the miniatures
- entries for all the miniatures.
The problem I'm having is that I don't know how to query the database with a LEFT JOIN using a "Rails method" so that if a user doesn't have an entry for a miniature in Ownerships it defaults to a have_count of 0. Currently I query for each user_id/miniature_id combination individually as I loop through all miniatures and it's obviously really inefficient.
View:
<% for miniature in #miniatures %>
<td><%= link_to miniature.name, miniature %></td>
<td><%= text_field_tag "counts[#{miniature.id}]", get_user_miniature_count(current_user, miniature), :size => 2 %></td>
<% end %>
Helper:
def get_user_miniature_count(user, miniature)
ownerships = user.ownerships
ownership = user.ownerships.find_by_miniature_id(miniature.id)
if ownership.nil?
return 0
else
return ownership.have_count
end
end
An alternate solution would be creating entries for all miniatures when a user signs up, but then I would also have to add a 0 have_count for all users when a new miniature is added to the database after they sign up. That seems like it could get a bit complex, but perhaps it's the right way to go?
Is there a way to do the join and supply a default value for miniatures where there's no entries in the Ownership table for that particular user?
The first thing I would say is that the User model should own the code that works out how many of a given miniature the user owns, since it seems like "business logic" rather than view formatting.
My suggestion would be to add a method to your User model:
def owns(miniature_id)
o = ownerships.detect { |o| o.miniature_id == miniature_id }
(o && o.have_count) || 0
end
Dry-coded, ymmv.
Edit: Note that ownerships is cached by Rails once loaded and detect is not overridden by ActiveRecord like find is, and so acts as you would expect it to on an Array (ie no database operations).
Using fd's suggestion and information found at http://www.ruby-forum.com/topic/52385, I created the following method:
def miniature_count(miniature_id)
if #counts.nil?
#counts = Hash.new
ownerships.collect{|o| #counts[o.miniature_id] = o.have_count }
end
count = #counts[miniature_id] || 0
end
This ends up being faster than the detect approach.
I picked miniature_count over owns for the name because owns sounds like a method that should return a boolean instead of an integer.
Query Every Entry
Completed in 2.61783 (0 reqs/sec) | Rendering: 1.14116 (43%) | DB: 1.34131 (51%) | 200 OK [http://ddmdb/collection/1]
Detect Methods
Completed in 2.20406 (0 reqs/sec) | Rendering: 1.87113 (84%) | DB: 0.21206 (9%) | 200 OK [http://ddmdb/collection/1]
Hash Method
Completed in 0.41957 (2 reqs/sec) | Rendering: 0.19290 (45%) | DB: 0.10735 (25%) | 200 OK [http://ddmdb/collection/1]
I will definitely need to add caching, but this is definitely an improvement. I also suspect I am prematurely optimizing this code, but it's a small site and a 2.5 second load time was not making me happy.
Maybe I'm missing something, but the way you've specified the relationships seems sufficient for rails to figure out the counts on its own? Have you tried that?
edit:
Re the discussion in the comments...how about this:
<% ownerships=current_user.ownerships %>
<% for miniature in #miniatures %>
<td><%= link_to miniature.name, miniature %></td>
<td><%= text_field_tag "counts[#{miniature.id}]", get_miniature_count(ownerships, miniature), :size => 2 %></td>
<% end %>
Where get_miniature_count() just iterates through the supplied ownerships and returns 0 or the count if the miniature appears in the list? I think this will avoid going back to the DB again in each iteration of the 'for'.
edit 2: I'd also suggest firing up script/console and trying to do what you want in ruby directly, i.e. test for the miniatures membership in the ownerships list thinking in terms of ruby not SQL. Often, rails and activerecord is smart enough to do the necessary SQL black magic for you behind the scenes, given it knows the relationships. If you find a user and then do user.methods you'll see what is available.