Rails - Creating Extra Instance Variables in Controller vs Accessing Relationships in View - sql

Hi thanks for viewing my question. I am trying to figure out if there are any performance benefits (or any other benefits) to creating more instance variables in a controller rather than accessing relationships in the view with the model methods. Here's an example of what I mean
You could go this way:
# posts_controller.rb
def show
#post = Post.find(id)
#comments = #post.comments
end
# posts/show.html.erb
<%= #comments.each do |c| %>
...
Or, alternatively:
# posts_controller.rb
def show
#post = Post.find(id)
end
# posts/show.html.erb
<%= #post.comments.each do |c| %>
...
Does either of these approaches have a performance benefit? When should you decide to create an additional instance variable rather than accessing data through the model's methods in the view? Any other reason to pick one over the other? Is there a better approach? Asking for educational purposes and lack of Google answers.
Thanks!

Any call which queries the db is not recommended from views, that's the reason it is separated M-V-C, it is controller who should query the db and provide readymade object to the view using instance variables.
In the mentioned example above, although first is better option, it will query db twice, in such cases you should include the comments in the same call.
def show
#post = Post.find(id).includes(:comments)
end
then in your view,
<%= #post.comments.each do |c| %>
is good because it has preloaded comments beforehand..
Fetching all required data in controller also helps in reducing N+1 query problems..

Related

Ordering records when using has_and_belongs_to_many relation in Rails 3?

I am, for the first time, trying to use a HABTM relationship in my Rails application. The following image shows the models and their relations to each other:
Currently I am displaying all members within a region with the following:
<% #region.members.each do |member| %>
<%= link_to member.name, member %>
<% end %>
I am trying to sort/order the list of members by their respective level. I then would like to have the members ordered in descending alphabetical order.
So, the members_controller code is currently the default:
#members = Member.all
I can order the results into alphabetical order:
#members = Member.order("name DESC").all
but I can't figure out how to use a related model's data to order the member records. There are likely going to be three levels; Charity, Basic and Subscription. I am planning on having a block at the top of the page which only shows subscription members. The subscription members should not then be shown again on the page.
#subscribed_members = Member.where(:level == 1).order("name DESC").all
but, as you can see, I have no idea how to filter that block by level.
Can anyone point me in the right direction, or maybe to a tutorial for this? I've read through the Rails guide for HABTM relationships but it only seems to cover the models.
EDIT
After trying the suggestion, I now have the following:
#members = Member.all
#subscribedmembers = Member.include(:levels)
.where("levels.name == 'subscriber'")
How do I use that method in the regions view?
<% #region.subscribedmembers.each do |member| %>
<%= member.name %>
<% end %>
That won't work because it's looking for a related model called subscrivedmembers which doesn't exist.
For this kind of task, i recommend you to take a look at joining models with ActiveRecord. The methods are include and joins. Lemme demonstrate with a piece of code:
#subscribed_members = Member.include(:levels)
.where("members.url == 'test_url'")
.order("levels.name DESC")
If you check your console after this query is run, you can see the include makes a SQL join with the two tables. Here i am assuming member :has_many :levels, so you include the :levels table and use a prefixed column name on your order clause.
It is not that hard once you get the idea, so i encourage you to try these two methods on the console and check the results.
EDIT
Create a scope with you query code, then use it on any place. Example:
# On your model
scope :ultimate_level, include(:levels).where("name = 'test'").order("levels.name DESC")
# On your controller
#subscribed = Member.ultimate_level
# On your view
<% #subscribed.each do |s| %>
You can create different scopes too, each one that makes a single operation, and then reuse on our controllers, etc.
Obs.: Check the syntax, i didn't test the code myself.

ActiveRecord "select" results of model method

I have a Rails app that pulls in music from Soundcloud. This data contains a title, which I save as mix.sc_title but it's not always properly formatted. I have added an additional attribute on my Mix model which I call mix.override_title
For display on my site, I want to use the override title if available, and the sc_title in all other cases.
I have a Mix model method to do this for me
def display_title
override_title.blank? sc_title : override_title
end
Mixes#index grabs #mixes = Mix.where(:active => true) and mixes/index.html.erb looks like this:
<ul>
<% #mixes.each do |mix| %>
<li><%= link_to mix.display_title, mix %></li>
<% end %>
</ul>
As you can see, I'm not directly using any mix attributes, and so I take a huge hit when I go to the DB, and I don't actually benefit from it.
Is there a leaner way to get just the information I need? (mix.display_title)
I have tried Mix.select("display_title").where(:active => true) but it fails because display_title is not a real DB column
You can do Mix.select("sc_title, override_title").where(:active => true) and it will work, since those are the actual fields that the method uses. I don't really think getting the additional attributes gives you that much of a DB hit but sometimes selecting only what you need can be beneficial.
As you start chaining on more Arel commands, consider putting the select into a model method:
def select_active_titles
select("sc_title, override_title").where(:active => true)
end
Edit: Your link_to helper also secretly calls mix.id to link to the right mix, so make sure it's working and if not add id to the list of selected attributes.

Rails - uncached controller action messing with serialized attribute

I have a controller action that calls a model method which generates a serialized list of data pulled from another model database. I need this to be uncached because the SQL queries should be random data pulls.
Here's a general idea of my code (Note that User has_one Foo, Bar is an arbitrary model of data, :data_list is of type text, and the database is SQLite):
# app/models/foo.rb
class Foo < ActiveRecord::Base
serialize :data_list
def generate_data
list = []
for i in 1..4
data = Bar.find(:first, :order => "Random()")
list << data
end
self.data_list = list
end
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def generate_action
...
uncached do
#user.foo.generate_data
end
#user.foo.save
end
end
# app/views/user/show.html.erb
...
<% #user.foo.data_list.each do |data| %>
<%= data %><br />
<% end %>
Whenever uncached do ... end is removed, everything works fine and the show view prints out each set of Bar objects in #user.foo.data_list. Unfortunately, because of Rails' SQL caching, it ends up look like this:
RandomDataPoint8
RandomDataPoint8
RandomDataPoint8
RandomDataPoint8
When I need to look like this:
RandomDataPoint7
RandomDataPoint13
RandomDataPoint2
RandomDataPoint21
It should be noted that running user.foo.generate_data from Rails command line works perfectly with the randomization. It is only when being called from the controller that caching starts to occur.
My research suggested I use uncached in the controller to remove caching, however it seems to destroy my data serialization and I receive the error:
undefined method 'each' for #<String:0x007ff49008dc70>
In fact, it does this even if I retroactively add in uncached (having successfully generated a data_plan without uncached prior) and save the controller, but don't call generate_action.
EDIT
I believe this problem is actually related to the fact that I was storing an object in the hash. Switching to the object id fixed this problem. Another SO question of mine regarding this can be found here:
Rails - Accessing serialized data from console
The following has been preserved just because the syntax may still help people, but I don't believe it was the actual cause of the problem.
I solved this by moving uncached to the model. For reference, the source I was using to originally solve this problem was this link: http://railspikes.com/2008/8/18/disabling-activerecord-query-caching-when-needed
What I overlooked is that he puts uncached in the model, not the controller. Also, the syntax needed to be a little different:
# app/models/foo.rb
self.class.uncached do
...
end
instead of
uncached do
...
end
The source for the syntax correction is this SO response: https://stackoverflow.com/a/967690/337903

rails nested resource and routes for initialized resource

I have a problem with the normal way rails operates when using nested forms / resources and routing.
I have two tables, Words and Definitions...
Words have many definitions, but I do not create a Word until it has at least one definition.
Everything on the model and controller end works but I cannot figure out how to handle the form helpers.
<%= semantic_form_for [#word, #definition] do |f| %>
This works perfectly but only if #word actually exists and is not a new UNSAVED record. IE in the controller I am doing a find_or_initialize_by call for Word then building a definition off of that.
<%= semantic_form_for [:word, #definition] do |f| %>
This words but only if the word doesn't exist. IE if I try to edit using this construction I get an odd url (which doesn't work). words/12345/definition/12345
I tried using the url_for helper but had similar results as above...
Any other ideas?
Mongoid doesn't initialize embedded documents by default. You need to build them yourself most likely with a callback in your Word model:
after_initialize :build_definition
def build_definition
self.definitions.build unless self.definitions.any?
end
If you wanna stay CRUD and allow definitions to be created before words, you must duplicate routes for definitions, one inside words and one outside, so you can do:
<%= semantic_form_for [#definition] do |f| %>

Baffled by results of the render command in Rails

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.