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.
Related
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 } })
I have this query that works but I would like to expand it so that I can check for multiple ids such that I pass in a vector of ids. [1,2,3,5] etc... I have tried using SQL IN with no luck.
EventType.find(3).events.all(:include => {:sheet => :rink}, :conditions => ["rinks.id = ?", 2])
You were on the right track with IN. Here's syntax that will work in Rails 3+:
EventType.find(3).events.where("id IN (?)", [1,2,3]).include(:sheet => :rink)
Improvement from a comment removes SQL entirely:
EventType.find(3).events.where(:id => [1,2,3]).include(:sheet => :rink)
My question is twofold... Primarily, I am trying to figure out how to ask > or < when filtering this query. You can see at the end I have .where(:created_at > 2.months.ago) and that is improper syntax, but I'm not sure the correct way to call something similar.
Secondly, this is a bit of a long string and is going to get longer as the are more conditions I have to factor in. Is there a cleaner way of building this, or is a long string of conditions like this pretty standard?
class PhotosController < ApplicationController
def showcase
#photos = Photo.order(params[:sort] || 'random()').search(params[:search]).paginate(:per_page => 12, :page => params[:page]).where(:created_at > 2.months.ago)
end
Thanks.
Unfortunately you've hit a sore point in the ActiveRecord querying api. There is no standard, out of the box way to do this. You can do date ranges very easily, but < and > have no easy path. However Arel, the underlying SQL engine, can do this very easily. You could write a simple scope to handle it thusly:
scope :created_after, lambda {|date| where arel_table[:created_at].gt(date) }
And you could refactor this easily to take a column, or gt versus lt, etc.
Other people have solved this problem already, however, and you could take advantage of their work. One example is MetaWhere, which adds a bunch of syntactic sugar to your queries. For example, using it you might write:
Article.where(:title.matches => 'Hello%', :created_at.gt => 3.days.ago)
On #2, scopes do tend to get long. You might look into the gem has_scope, which helps to alleviate this by defining scopes on the controller in an analogous way to how they are defined on the model. An example from the site:
# The model
# Note it's using old Rails 2 named_scope, but Rails 3 scope works just as well.
class Graduation < ActiveRecord::Base
named_scope :featured, :conditions => { :featured => true }
named_scope :by_degree, proc {|degree| { :conditions => { :degree => degree } } }
end
# The controller
class GraduationsController < ApplicationController
has_scope :featured, :type => :boolean
has_scope :by_degree
def index
#graduations = apply_scopes(Graduation).all
end
end
You can do where(["created_at > ?", 2.months.ago]) for your first question.
For your second question there are several solutions :
You can use scopes to embed the conditions in them and then combine them.
You can break the line in multiple lines.
You can keep it like this if you have a large screen and you don't work with any other people.
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.
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.