Making this ActiveRecord lookups more efficient - ruby-on-rails-3

I want to get an array of all email addresses for users of certain service types.
Using a string of ActiveRecord has_many relations, I can get these like this:
affected_services = Service.where(service_type: 'black')
affected_accounts = affected_services.map {|s| s.account}
affected_emails = affected_accounts.map {|a| a.contact.email}
I know it would be a fairly simple SQL query, but I'd prefer to see if ActiveRecord can do it to keep database abstraction.
Is there a good ActiveRecord way to retrieve those results?

You could use :include to include children in the query.
Account.find(:all, :include => :contact, :conditions => {:service_id => Service.where(:service_type => 'black').map{|account| account.contact.email } })

Related

Rails sort collection by association count

I'm working in Rails 4, and have two relevant models:
Account Model
has_many :agent_recalls, primary_key: "id", :foreign_key => "pickup_agent_id", class_name: "Booking"
Hence, queries like Account.find(10).agent_recalls would work.
What I want to do is sort the entire Account collection by this agent_recalls association.
Ideally it'd look something like (but obviously not):
#agents = Account.where(agent: true).order(:agent_recalls)
Question: What's the correct query to output an ordered list, by this agent_recall count?
Well to accomplish what you are looking for you have 2 options:
first, only a query, but it will implied a join, so there will be lost the Accounts that doesn't have any agent_recalls, so i will discard this option
second, i think this one is more appropriate for what you are trying to do
Account.find(:all, :conditions => { :agent => true }, :include => :agent_recalls).sort_by {|a| a. agent_recalls.size}
As you can see is a mix between a query and ruby, hope it helps :)

Cancan Thinking Sphinx current_ability Questions

trying to get cancan working with thinking sphinx but running into some issues.
Before using sphinx, I had this in my companies view:
#companies = Company.accessible_by(current_ability)
That prevented my users from seeing anyone else's companies...
After installing sphinx, I ended up with:
#companies = Company.accessible_by(current_ability).search(params[:search], :include => :order, :match_mode => :extended ).paginate(:page => params[:page])
Which now displays all my companies and isn't refining per user based on ability.
It would see ts isn't set up for cancan?
I think it's more that accessible_by is probably a scope - which is Database/SQL-driven. Sphinx has its own query interface, and so ActiveRecord scopes don't apply.
An inefficient workaround (gets all companies first):
company_ids = Company.accessible_by(current_ability).collect &:id
#companies = Company.search params[:search],
:include => :order,
:match_mode => :extended,
:page => params[:page],
:with => {:sphinx_internal_id => company_ids}
A couple of things to note: sphinx_internal_id is the indexed model's primary key - Sphinx has its own unique identifier named id, hence the distinction. Also: You don't want to call paginate on a search collection - Sphinx always paginates, so just pass the :page param through to the search call.
There'd be two better workarounds that I can think of - either have a Sphinx equivalent of accessible_by, with the relevant information added to your indices as attributes - or, simpler if not quite as ideal, just get the company ids returned in the first line of my above snippet without loading up every company as an ActiveRecord object. Both will probably mean bypassing and/or duplicating Cancan's helpers.
Although... maybe this would do the trick, taking the latter approach:
sql = Company.accessible_by(current_ability).select(:id).to_sql
company_ids = Company.connection.select_values sql
#companies = Company.search params[:search],
:include => :order,
:match_mode => :extended,
:page => params[:page],
:with => {:sphinx_internal_id => company_ids}
Avoids loading unnecessary Company objects, uses the Cancan helper (provided it is/returns a scope), and works neatly with what Sphinx/Thinking Sphinx expects. I've not used Cancan though, so this is a bit of guesswork.

Should I drop a polymorphic association?

My code is still just in development, not production, and I'm hitting a wall with generating data that I want for some views.
Without burying you guys in details, I basically want to navigate through multiple model associations to get some information at each level. The one association giving me problems is a polymorphic belongs_to. Here are the most relevant associations
Model Post
belongs_to :subtopic
has_many :flags, :as => :flaggable
Model Subtopic
has_many :flags, :as => :flaggable
Model Flag
belongs_to :flaggable, :polymorphic => true
I'd like to display multiple flags in a Flags#index view. There's information from other models that I want to display, as well, but I'm leaving out the specifics here to keep this simpler.
In my Flags_controller#index action, I'm currently using #flags = paginate_by_sql to pull everything I want from the database. I can successfully get the data, but I can't get the associated model objects eager-loaded (though the data I want is all in memory). I'm looking at a few options now:
rewrite my views to work on the SQL data in the #flags object. This should work and will prevent the 5-6 association-model-SQL queries per row on the index page, but will look very hackish. I'd like to avoid this if possible
simplify my views and create additional pages for the more detailed information, to be loaded only when viewing one individual flag
change the model hierarchy/definitions away from polymorphic associations to inheritance. Effectively make a module or class FlaggableObject that would be the parent of both Subtopic and Post.
I'm leaning towards the third option, but I'm not certain that I'll be able to cleanly pull all the information I want using Rails' ActiveRecord helpers only.
I would like insight on whether this would work and, more importantly, if you you have a better solution
EDIT: Some nit-picky include behavior I've encountered
#flags = Flag.find(:all,:conditions=> "flaggable_type = 'Post'", :include => [{:flaggable=>[:user,{:subtopic=>:category}]},:user]).paginate(:page => 1)
=> (valid response)
#flags = Flag.find(:all,:conditions=> ["flaggable_type = 'Post' AND
post.subtopic.category_id IN ?", [2,3,4,5]], :include => [{:flaggable=>
[:user, {:subtopic=>:category}]},:user]).paginate(:page => 1)
=> ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :flaggable
Don't drop the polymorphic association. Use includes(:association_name) to eager-load the associated objects. paginate_by_sql won't work, but paginate will.
#flags = Flag.includes(:flaggable).paginate(:page => 1)
It will do exactly what you want, using one query from each table.
See A Guide to Active Record Associations. You may see older examples using the :include option, but the includes method is the new interface in Rails 3.0 and 3.1.
Update from original poster:
If you're getting this error: Can not eagerly load the polymorphic association :flaggable, try something like the following:
Flag.where("flaggable_type = 'Post'").includes([{:flaggable=>[:user, {:subtopic=>:category}]}, :user]).paginate(:page => 1)
See comments for more details.
Issues: Count over a polymorphic association.
#flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1)
Try like the following:
#flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1, :total_entries => Flag.count(:conditions =>
["flaggable_type = 'Post' AND post.subtopic.category_id IN ?", [2,3,4,5]]))

Rails/AR find where habtm does not include

I have a Rails app with Users, and each user HABTM Roles.
I want to select Users without a specific role. I have searchlogic at my disposal, and I'm lost. I've tried using a combination of conditions and joins and includes and what not, but I can't seem to nail it. This works:
User.find(:all, :conditions => ['role_id != ?', Role[:admin].id], :joins => :roles)
To find users that are not admins, but doesn't not find users with no roles (which I want to find as well).
What simple thing am I missing in my tired state?
Use a sub-query and the NOT IN operator
User.find(:all,:conditions => ["id NOT IN (select user_id from roles_users where role_id = ?)", Role[:admin].id)
How about this:
User.find :all, :conditions => [ 'roles.id is ? or roles.id != ?', nil, Role[:admin].id ], :include => :roles
This works for has_many :through, seems like it should be the same for HABTM.
I can do
User.all - User.find(:all, :conditions => ['role_id = ?', Role[:admin].id], :joins => :roles)
Which accomplishes what I want in two queries, which is probably fine for this project, but if I can get it to a single query it would be nice.

rails active record nuances and protecting against injection attacks

When I make a query...
is there any meaningful difference between using a find_by helper or not?
Are there any reasons I'm overlooking for opting for shorter lines of code when doing things like this?
Booking.find_all_by_user_id(1, :joins => :confirmation)
Booking.find(:all, :joins => :confirmation, :conditions => [ 'bookings.user_id = ?', 1] )
No, regarding injection attacks.
The find_by method should be safe. However the only killer mistake is to use user input directly inside your conditions param when using find method, like doing:
Booking.find(:all, :joins => :confirmation, :conditions => [ 'bookings.user_id = #{params[user_id]]}'] )
Of course the right one is the way you did it and find method will filter things up.
Booking.find(:all, :joins => :confirmation, :conditions => [ 'bookings.user_id = ?', params[user_id]] )
What you're looking for is in here:
http://guides.rubyonrails.org/security.html#sql-injection
AND
http://guides.rubyonrails.org/security.html#mass-assignment
Be sure to read both carefully.