Rails - Active Record :conditions overrides :select - ruby-on-rails

I have a fairly large model and I want to retrieve only a select set of fields for each record in order to keep the JSON string I am building small.
Using :select with find works great but my key goal is to use conditional logic with an associated model. Is the only way to do this really with a lamda in a named scope? I'm dreading that perhaps unnecessarily but I'd like to understand if there is a way to make the :select work with a condition.
This works:
#sites = Site.find :all, :select => 'id,foo,bar'
When I try this:
#sites = Site.find :all, :select => 'id,foo,bar', :include => [:relatedmodel],
:conditions => ["relatedmodel.type in (?)", params[:filters]]
The condition works but each record includes all of the Site attributes which makes my JSON string way way too large.
Thanks for any pointers!

The to_json call supports :except and :only options to exclude/include model fields during serialization.
#sites.to_json(:only => [:name, :foo, :bar])
Call above serializes the Site objects with fields name and location.
#sites.to_json(:only => [:name, :location],
:include => { :relatedmodel => {
:only => [:description]
}
}
)
Call above serializes the Site objects with fields name, and location and contained RelatedModel objects with description field.

Related

Rails 3 to_json include with condition/where clause

I am using the following to output a list of JSON records:
#team.people.to_json(
:include => [:user, :statistics => {:include => :attribute}]).html_safe
However, I would like to only include statistics that have a certain type_id set on them. Essentially a left outer join with the users and the statistics, where the a type_id on the statistic equals some number.
I can think of at least a couple options:
In the Person model, override to_json (or, perhaps better yet, serializable_hash) and do your conditional there.
Instead of {:include => :attribute} do {:methods => :foo} and do your conditional in foo.
Here's an example of where I overrode serializable_hash, if it helps:
def serializable_hash(options={})
options = {
:methods => [
'client',
'services',
'products',
'has_payments',
]}.update(options)
super(options)
end
I could imagine something above options = where you set the methods array to one thing if type_id is the number you're looking for, or to something else otherwise.

Eager loading and map in rails 3

If I wanted to eagerly load a collection in rails and render it in json, I would have to do something like this.
#photos = #event.photos.to_json(:include =>
{:appearances => {:include => :person}}
)
What if I wanted to map this collection? As you can see it's no longer a collection, but a json string. Prior to this necessary eager loading, I was doing the following:
#photos = #event.photos.map{|photo|
photo['some_funky_stuff'] = photo.funky_calculation
photo
}
But, I can't seem to be able to do the two things together:
#event.photos.map{|photo|
photo['some_funky_stuff'] = photo.funky_calculation
photo
}.to_json(:include =>
{:appearances => {:include => :person}}
)
The above does not show 'appearances' ( the eagerly loaded join record )... How do I do these two together? Many thanks!
You may have the term "eager loading" mixed up a little bit. As previous answers have mentioned, you need to use it on the association for it to be eager loaded. However, when you use :include in the to_json call, you will still end up with the same result, no matter if it is eager or not.
But to answer your question, for the to_json method to both include the appearances and the funky_calculation you can combine it with :methods instead. Try it like this:
#photos = #event.photos.to_json(
:include => {:appearances => {:include => :person}},
:methods => [: funky_calculation]
)
And if you want increased performance (eager loading), then use include on the associations as well:
#photos = #event.photos.includes(:appearances => :person).to_json(
:include => {:appearances => {:include => :person}},
:methods => [: funky_calculation]
)
You could eager load using includes after has_many association
#photos = #event.photos.includes(:appearances => [:person]).to_json
You might want to try using joins() or includes() on photos, instead as an option to to_json().
http://guides.rubyonrails.org/active_record_querying.html#using-array-hash-of-named-associations

Rails child object query leads to N+1 problem

while writing some application for personal use. I find out the child query is not as great as it look.
For instance, I have 2 object
Category has_many Files
File belongs_to Category
File.category will access its parent category. But this lead to the famous N+1 problem.
For example, I want my homepage to list out 20 newest files and its corresponding category using something like this
for file in #files
%p
= file.name
= file.category.name
How should I solve this problem?
#files = File.find(:all, :limit => 20, :order => "created at desc", :include => :category)
In your find if you say :include => :category then this will eager load the categories for you and avoid a separate query to retrieve each category's name. So for your example of the 20 most recent files you could do:
#files = File.find :all, :limit => 20, :include => :category,
:order => 'created at desc'

rails find: using conditions while including same table twice via different named associations

I have posts which are sent by users to other users. There are two models - :post and :user, and :post has the following named associations:
belongs_to :from_user, :class_name => "User", :foreign_key => "from_user_id"
belongs_to :to_user, :class_name => "User", :foreign_key => "to_user_id"
Both :user and :post have "is_public" column, indicating that either a single post and/or the entire user profile can be public or private.
My goal is to fetch a list of posts which are public AND whose recipients have public profiles. At the same time, I would like to "include" both sender and recipient info to minimize the # of db calls. The challenge is that I am effectively "including" the same table twice via the named associations, but in my "conditions" I need to make sure that I only filter by the recipient's "is_public" column.
I can't do the following because "conditions" does not accept an association name as a parameter:
Post.find(:all, :include => [ :to_user, :from_user ],
:conditions => { :is_public => true, :to_user => { :is_public => true }})
So, one way I can accomplish this is by doing an additional "join" on the "users" table:
Post.find(:all, :include => [ :to_user, :from_user ],
:joins => "inner join users toalias on posts.to_user_id = toalias.id",
:conditions => { :is_public => true, 'toalias.is_public' => true })
Is there a better, perhaps cleaner, way to do this?
Thanks in advance
I was facing the same problem and found a solution after watching sql query generated from rails query, sql query automatically generates an alias
try this,
Post.find(:all, :include => [ :to_user, :from_user ],
:conditions => { :is_public => true, 'to_users_posts.is_public' => true })
It worked for me :)
I have not been able to find a better solution than the one originally stated in my question. This one doesn't depend on how Rails names/aliases tables when compiling a query and therefore appears to be cleaner than the alternatives (short of using 3rd party gems or plugins):
Post.find(:all, :include => [ :to_user, :from_user ],
:joins => "inner join users toalias on posts.to_user_id = toalias.id",
:conditions => { :is_public => true, 'toalias.is_public' => true })
If you are on Rails 3, take a look at squeel gem, esp if you are doing these kind of complex joins often. Or if you dont want to add a extra gem, take look at the Arel table in Rails 3.
I very pleasant for that question, because I've tried to find proper solution about 2h, so after using few tips above, I've found the proper, in my case, solution. My case:
I need filter instances by created_by_id/updated_by_id fields, that fields are in every table, so...what I did
In concern 'Filterable' I wrote the method and when I needed filter by that fields I used that ->
key = "#{key.pluralize}_#{name.pluralize.downcase}.email" if %w(created_by updated_by).include?(key)
# in case with invoices key = 'updated_bies_invoices.email'
results = results.eager_load(:created_by, :updated_by).where("#{key} = ?", value)

Rails find by *all* associated tags.id in

Say I have a model Taggable has_many tags, how may I find all taggables by their associated tag's taggable_id field?
Taggable.find(:all, :joins => :tags, :conditions => {:tags => {:taggable_id => [1,2,3]}})
results in this:
SELECT `taggables`.* FROM `taggables` INNER JOIN `tags` ON tags.taggable_id = taggables.id WHERE (`tag`.`taggable_id` IN (1,2,3))
The syntax is incredible but does not fit my needs in that the resulting sql returns any taggable that has any, some or all of the tags.
How can I find taggables with related tags of field taggable_id valued 1, 2 and 3?
Thanks for any advice. :)
UPDATE:
I have found a solution which I'll post for others, should they end up here. Also though for the attention of others whose suggestions for improvement I'd happily receive. :)
Taggable.find(:all, :joins => :tags, :select => "taggables.*, tags.count tag_count", :conditions => {:tags => {:taggable_id => array_of_tag_ids}}, :group => "taggables.id having tag_count = #{array_of_tag_ids.count}"))
Your question is a bit confusing ('tags' seemed to be used quite a bit :)), but I think you want the same thing I needed here:
Taggable.find([1,2,3], :include => :tags).tags.map { |t| t.taggables }
or (if you want the results to be unique, which you probably do):
Taggable.find([1,2,3], :include => :tags).tags.map { |t| t.taggables }.flatten.uniq
This gets at all the taggables that have the same tags as the taggables that have ids 1,2, and 3. Is that what you wanted?

Resources