ActiveRecord Includes n+1 issues - ruby-on-rails

I am using the Bullet gem to assist me in finding my n + 1 errors for my ActiveRecord queries. I currently am passing in:
#user = User.includes(:routines => {:lifts => [:exercise, :infos]}).find(current_user.id)
To me this means I am loading the current user, his routines, those routines' lifts, and those lifts' exercise and infos (which are sets).
Is my assumption true?
The Bullet gem is giving me two errors in which it is claiming I need to:
Lift => [:routine] so it says add ".include => [:routine]"
AND
Lift => [:infos] so it says add ".include => [:infos]"
Would somebody be able to explain this to me?
Thank you!

You definitely are on the right path. I highly recommend brushing up on this via http://guides.rubyonrails.org/
Your setup supports pre-loading when you access data like this:
routines = #user.routines
lifts = #user.routines.map(&:lifts)
Can you please describe how you are attempting to access this data? It appears that you may be trying to access routine via:
lift.routine
How are you accessing lift?
You might want to make sure that you use :inverse_of when specifying your associations.
Does Routine have many :lifts?

Related

Rails: OR where condition on association

I have a model called Facility. It has_many :addresses. The Address model has_one :city.
Now I want to run a condition:
to get all facilities that do not have associated address;
if they do have addresses check if they do not have city model associated with that addresses.
I have tried the first condition but I am unable to combine an OR for it.
This gets all facilities that do not have an address model associated to it
Facility.includes(:addresses).where( :addresses => {:facility_id => nil})
Some error tries are:
Facility.includes(:addresses).where( :addresses => ({:facility_id => nil}).or({:city_id => nil}) );
Facility.includes(:addresses).where( :addresses => ({:facility_id => nil}).or(:address => {:city_id => nil}) )
Try the following:
Facility.includes(:addresses)
.where('addresses.facility_id is null or addresses.city_id is null')
.references(:addresses)
You can also find interesting this post, concerning possible implementations of the or condition in the activerecord queries.
Try this
Facility.includes(addresses: :city).where("addresses.facility_id IS NULL OR addresses.city_id IS NULL");
Hope this would be helpful.
First, using includes when you want to do conditions on associations can have negative side effects:
You will eager load data, which, if not needed, means doing more work for nothing
The addresses that are eager loaded are only those matching the condition. So if you thn use addresses, you don't get all of them for that record (only those that matched the condition). This causes weird bug or hard to understand working code.
To avoid this and all other issues, I recommend to use a gem I made specifically for this: activerecord_where_assoc
Your problem can be simplified, I will do so after replying to your request as-is:
You seem to be using Rails 4.2 according to the tags. So you don't yet have access to the #or method yet. So this is how you could do it:
sql_no_address = Facility.assoc_not_exists_sql(:addresses)
sql_no_city = Facility.assoc_exists_sql(:addresses) { where_assoc_not_exists(:city) }
Facility.where("#{sql_no_address} OR #{sql_no_city}")
If you have Rails 5 or more:
Facility.where_assoc_not_exists(:addresses).or(Facility.where_assoc_not_exists([:addresses, :city]))
Now, a simpler solution. Notice that if a facility has no addresses, then it cannot have a city, because it must go through an address to get to a city. Really, your problem is just "I want Facilities that have no cities". This is what passing an array to the methods do, it tries to go from one to the other until the end.
Facility.where_assoc_not_exists([:addresses, :city])
Here are the introduction and examples. Read more details in the documentation.

Optimizing Finding Results in Rails 2.3

My question is regarding the way Rails handles queries in Rails 2.3.
I am currently looking at some legacy code and wanted to try see if there was a better way to go about this rather than using the collection that the previous programmer used.
As a Rails 3 minded person I think there should be a better way to do this. To me this seems like it would be a costly operation to run, but maybe the Rails way of doing it uses the same method so calling it would only be a convenience
def self.entity_assigned(entities)
return nil if entities.size == 0
conditions = "#{EntityUser.table_name}.entity_id IN (#{entities.collect{|x| x.id}.join(',')})"
find :all,
:include => [:entity_users => :entity],
:conditions => conditions
end
If someone can let me know if there is a better way to do this or if I should continue with the current way.

Rails Active Resources ignore conditions

I have been searching a solution for this issue but in no vain. Basically, I am trying to do some searches using Active Resources eg:
File.find(:all, :params => {:file_name => "blah"})
or:
File.find(:all, :conditions => {:file_name => "blah"})
File is an Active Resource object
I expect the result to be filtered but the output is the same as File.find(:all)(conditions are completely ignored). Has anyone experienced a similar problem?I am using rails 3.0.7, this code is called from a web app which is talking to another API server using AR.
Any suggestions will be much appreciated
Thanks
Dont know if i got your point. I think this should do:
File.where file_name: "blah"
I have the same type of setup, and tons of searching in my application where large results need to get filtered - however, I have been accomplishing this using the API Server side.
So calls would look like:
File.find(:all, :params => {:file_name => "blah"})
Which would be translated into some search URL by rails:
http://someip:port/someurl/files/?file_name=blah
So the API would receive this with the file_name parameter and filter the result set based on that search, and then return those filtered results to the rails server.
I hope that at least helps in some way :)

Concurrency and Mongoid

I'm currently trying my hand at developing a simple web based game using rails and Mongoid. I've ran into some concurrency issues that i'm not sure how to solve.
The issue is i'm not sure how to atomically do a check and take an action based upon it in Mongoid.
Here is a sample of the relevant parts of the controller code to give you an idea of what i'm trying to do:
battle = current_user.battle
battle.submitted = true
battle.save
if Battle.where(opponent: current_user._id, submitted: true, resolving: false).any?
battle.update_attribute(:resolving, true)
#Resolve turn
A battle is between two users, but i only want one of the threads to run the #Resolve turn. Now unless i'm completely off both threads could check the condition one after another, but before setting resolving to true, therefore both end up running the '#Resolve turn' code.
I would much appreciate any ideas on how to solve this issue.
I am however getting an increasing feeling that doing user synchronization in this way is fairly impractical and that there's a better way altogether. So suggestions for other techniques that could accomplish the same thing would be greatly appreciated!
Sounds like you want the mongo findAndModify command which allows you to atomically retrieve and update a row.
Unfortunately mongoid doesn't appear to expose this part of the mongo api, so it looks like you'll have to drop down to the driver level for this one bit:
battle = Battle.collection.find_and_modify(query: {oppenent: current_user._id, ...},
update: {'$set' => {resolving: true})
By default the returned object does not include the modification made, but you can turn this on if you want (pass {:new => true})
The value returned is a raw hash, if my memory is correct you can do Battle.instantiate(doc) to get a Battle object back.

Rails - given an array of Users - how to get a output of just emails?

I have the following:
#users = User.all
User has several fields including email.
What I would like to be able to do is get a list of all the #users emails.
I tried:
#users.email.all but that errors w undefined
Ideas? Thanks
(by popular demand, posting as a real answer)
What I don't like about fl00r's solution is that it instantiates a new User object per record in the DB; which just doesn't scale. It's great for a table with just 10 emails in it, but once you start getting into the thousands you're going to run into problems, mostly with the memory consumption of Ruby.
One can get around this little problem by using connection.select_values on a model, and a little bit of ARel goodness:
User.connection.select_values(User.select("email").to_sql)
This will give you the straight strings of the email addresses from the database. No faffing about with user objects and will scale better than a straight User.select("email") query, but I wouldn't say it's the "best scale". There's probably better ways to do this that I am not aware of yet.
The point is: a String object will use way less memory than a User object and so you can have more of them. It's also a quicker query and doesn't go the long way about it (running the query, then mapping the values). Oh, and map would also take longer too.
If you're using Rails 2.3...
Then you'll have to construct the SQL manually, I'm sorry to say.
User.connection.select_values("SELECT email FROM users")
Just provides another example of the helpers that Rails 3 provides.
I still find the connection.select_values to be a valid way to go about this, but I recently found a default AR method that's built into Rails that will do this for you: pluck.
In your example, all that you would need to do is run:
User.pluck(:email)
The select_values approach can be faster on extremely large datasets, but that's because it doesn't typecast the returned values. E.g., boolean values will be returned how they are stored in the database (as 1's and 0's) and not as true | false.
The pluck method works with ARel, so you can daisy chain things:
User.order('created_at desc').limit(5).pluck(:email)
User.select(:email).map(&:email)
Just use:
User.select("email")
While I visit SO frequently, I only registered today. Unfortunately that means that I don't have enough of a reputation to leave comments on other people's answers.
Piggybacking on Ryan's answer above, you can extend ActiveRecord::Base to create a method that will allow you to use this throughout your code in a cleaner way.
Create a file in config/initializers (e.g., config/initializers/active_record.rb):
class ActiveRecord::Base
def self.selected_to_array
connection.select_values(self.scoped)
end
end
You can then chain this method at the end of your ARel declarations:
User.select('email').selected_to_array
User.select('email').where('id > ?', 5).limit(4).selected_to_array
Use this to get an array of all the e-mails:
#users.collect { |user| user.email }
# => ["test#example.com", "test2#example.com", ...]
Or a shorthand version:
#users.collect(&:email)
You should avoid using User.all.map(&:email) as it will create a lot of ActiveRecord objects which consume large amounts of memory, a good chunk of which will not be collected by Ruby's garbage collector. It's also CPU intensive.
If you simply want to collect only a few attributes from your database without sacrificing performance, high memory usage and cpu cycles, consider using Valium.
https://github.com/ernie/valium
Here's an example for getting all the emails from all the users in your database.
User.all[:email]
Or only for users that subscribed or whatever.
User.where(:subscribed => true)[:email].each do |email|
puts "Do something with #{email}"
end
Using User.all.map(&:email) is considered bad practice for the reasons mentioned above.

Resources