Array conditions for select on Active Record query - sql

I want to be able to specify user specific conditions in a MAX() sql call in the :select portion of the active record query (Rails 2.3.12). Unfortunately the safe string interpolation doesn't seem to work for the :select condition. The code is below, is there any other way to manually ensure the incoming values are safe? (They should be as they're just id's but you can never be too sure.)
:select => ["`component_instances`.*, max(`users`.id = ? AND `permissions`.view = 1) AS user_view, max(`users`.id = ? AND `permissions`.edit = 1) AS user_edit", user.id]
The query is designed to indicate to me if a particular user has explicit permissions on a particular item in the site. It'll either return 1 (they do), 0 (they don't but others do), or nil (no one has explicit permissions).

Turns out you can manually use the sanitize_sql_array method to do this:
:select => sanitize_sql_array(["`component_instances`.*, max(`users`.id = ? AND `permissions`.view = 1) AS user_view, max(`users`.id = ? AND `permissions`.edit = 1) AS user_edit", user.id, user.id])

Related

Rails ActiveRecord query to match all params

I need to have an ActiveRecord Postgres query that returns results which match all the parameters passed in through an array.
Some background: I have a User model, which has many Topics (through Specialties). I'm passing in the Topic ids as a string (Parameters: {"topics"=>"1,8,3"}) and then turning them into an array with .split(',') so I end up with topic_params = ["1","8","3"].
Now I'm trying to return all Users who have Topics that match/include all of those. After following the answer in this question, I managed to return Users who match ANY of the Topics with this:
#users = User.includes(:topics, :organization).where(:topics => {:id => topic_params})
But I need it to return results that match ALL. I'd also be open to better ways to accomplish this sort of task overall.
One way would be something like this
User.joins(:topics).where(topics: { id: [1, 2, 3] }).group('users.id').having('count(distinct topics.id) = 3')
Obviously I haven't your exact schema so you might have to tweak it a bit, but this is the basic setup.
Important is that the having clause counter must match the number of items you're matching with.

How do I search for keywords in rails?

I have a search form which works very well but I want to add keyword functionality to it. I'm not sure how. The search conditions look like this:
def search
conditions = {}
conditions[:targ_lang] = params[:targ_lang] unless params[:targ_lang].blank?
conditions[:inst_lang] = params[:inst_lang] unless params[:inst_lang].blank?
conditions[:start_level] = params[:start_level] unless params[:start_level].blank?
conditions[:end_level] = params[:end_level] unless params[:end_level].blank?
conditions[:skill] = params[:skill] unless params[:skill].blank?
conditions[:subject] = params[:subject] unless params[:subject].blank?
conditions[:inst_name] = params[:inst_name] unless params[:inst_name].blank?
conditions[:creator] = params[:creator] unless params[:creator].blank?
#conditions = Material.where("keywords like ?", "%#{keywords}") unless params[:keywords].blank?
#results = Material.find(:all, :conditions => conditions)
end
I've commented out the keyword line because it doesn't work at the moment. Obviously it's different to the others because we don't want to find an exact match, we want a single match from several possible keywords.
Can anyone help? Rookie question I know but I've been working on it for a while.

More efficient Active Record query for large number of columns

I'm trying to work out a more efficient way to add a note count, with a couple of simple where conditions applied to the query. This can take forever, though, as there are as many as 20K records to iterate over. Would welcome any thinking on this.
def reblog_array(notes)
data = []
notes.select('note_type, count(*) as count').where(:note_type => 'reblog', :created_at => Date.today.years_ago(1)..Date.today).group('DATE(created_at)').each do |n|
data << n.count
end
return data
end
This is what's passed to reblog_array(notes) from my controller.
#tumblr = Tumblr.find(params[:id])
#notes = Note.where("tumblr_id = '#{#tumblr.id}'")
From what I can tell, you are trying to calculate how many reblogs/day this Tumblr account/blog had? If so,
notes.where(:note_type => 'reblog', :created_at => Date.today.years_ago(1)..Date.today).group('DATE(created_at)').count.values
should give you the right result, without having to iterate over the result list again. One thing to note, your call right now won't indicate when there are days with 0 reblogs. If you drop the call to #values, you'll get a hash of date => count.
As an aside and in case you didn't know, I'd also suggest making more use of the ActiveRecord relations:
Class Tumblr
has_many :notes
end
#tumblr = Tumblr.find(params[:id])
#notes = #tumblr.notes
this way you avoid writing code like Note.where("tumblr_id = '#{#tumblr.id}'"). It's best to avoid string-interpolated parameters, in favour of code like Note.where(:tumblr_id => #tumblr.id) or Note.where("tumblr_id = ?", #tumblr.id) to leave less chance that you'll write code vulnerable to SQL injection

Rails SQL efficiency for where statement

Is there a more efficient method for doing a Rails SQL statement of the following code?
It will be called across the site to hide certain content or users based on if a user is blocked or not so it needs to be fairly efficient or it will slow everything else down as well.
users.rb file:
def is_blocked_by_or_has_blocked?(user)
status = relationships.where('followed_id = ? AND relationship_status = ?',
user.id, relationship_blocked).first ||
user.relationships.where('followed_id = ? AND relationship_status = ?',
self.id, relationship_blocked).first
return status
end
In that code, relationship_blocked is just an abstraction of an integer to make it easier to read later.
In a view, I am calling this method like this:
- unless current_user.is_blocked_by_or_has_blocked?(user)
- # show the content for unblocked users here
Edit
This is a sample query.. it stops after it finds the first instance (no need to check for a reverse relationship)
Relationship Load (0.2ms) SELECT "relationships".* FROM "relationships" WHERE ("relationships".follower_id = 101) AND (followed_id = 1 AND relationship_status = 2) LIMIT 1
You can change it to only run one query by making it use an IN (x,y,z) statement in the query (this is done by passing an array of ids to :followed_id). Also, by using .count, you bypass Rails instantiating an instance of the model for the resulting relationships, which will keep things faster (less data to pass around in memory):
def is_blocked_by_or_has_blocked?(user)
relationships.where(:followed_id => [user.id, self.id], :relationship_status => relationship_blocked).count > 0
end
Edit - To get it to look both ways;
Relationship.where(:user_id => [user.id, self.id], :followed_id => [user.id, self.id], :relationship_status => relationship_blocked).count > 0

Encapsulating SQL in a named_scope

I was wondering if there was a way to use "find_by_sql" within a named_scope. I'd like to treat custom sql as named_scope so I can chain it to my existing named_scopes. It would also be good for optimizing a sql snippet I use frequently.
While you can put any SQL you like in the conditions of a named scope, if you then call find_by_sql then the 'scopes' get thrown away.
Given:
class Item
# Anything you can put in an sql WHERE you can put here
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
end
This works (it just sticks the SQL string in there - if you have more than one they get joined with AND)
Item.mine.find :all
=> SELECT * FROM items WHERE ('user_id' = 887 and IS_A_NINJA() = 1)
However, this doesn't
Items.mine.find_by_sql 'select * from items limit 1'
=> select * from items limit 1
So the answer is "No". If you think about what has to happen behind the scenes then this makes a lot of sense. In order to build the SQL rails has to know how it fits together.
When you create normal queries, the select, joins, conditions, etc are all broken up into distinct pieces. Rails knows that it can add things to the conditions without affecting everything else (which is how with_scope and named_scope work).
With find_by_sql however, you just give rails a big string. It doesn't know what goes where, so it's not safe for it to go in and add the things it would need to add for the scopes to work.
This doesn't address exactly what you asked about, but you might investigate 'contruct_finder_sql'. It lets you can get the SQL of a named scope.
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
named_scope :additional {
:condtions => mine.send(:construct_finder_sql,{}) + " additional = 'foo'"
}
sure why not
:named_scope :conditions => [ your sql ]