Arel: How to cleanly join multiple conditions with OR? - ruby-on-rails

In my Rails app, I loop through an array to create a list of conditions that must be joined by OR. Below is the basic flow of how I currently do so.
conditions = nil
set.each do |value|
condition = value.to_condition
conditions = conditions ? conditions.or(condition) : condition
end
Obviously, it's not beautiful, but I still don't fully know my way around Arel. Does it offer any better way of OR-joining a set of dynamically-generated conditions?

This is a perfect fit for an inject which will give you a one-liner you can use within something else as well:
conditions = set.inject { |conds, cond| conds.or(cond) } which can even be written: set.inject(&:or) which is very nice.

There is also a useful plugin for this.
conditions_helper
It helps to generate complex conditions.

I think that's basically it. I'd initialize conditions to the base object to avoid the ternary:
scope = Article
set.each{|v| scope = scope.or(v.to_condition)}

Related

How can I get ActiveRecord to bind in raw statements?

So I'm basically doing this:
::OuterModel.where(%{
EXISTS(SELECT * FROM inner_model
WHERE outer_model.id = inner_model.outer_model_id)
AND inner_model.parameter = ?)
}, 1)
Now the issue becomes that this does text replacement in ActiveRecord, it doesn't bind ? to 1, which in turn is rendering ActiveRecords prepared statements pretty meaningless since every query has a different value of 1.
How can I get bind on my EXIST statements?
This is also of course true when doing something simple like:
::OuterModel.where('state = ?', 'active')
The alternative here isn't to do .join or generate IN, the performance of that is much worse, or wouldn't work in my actual use-case.
If you sure that this param is safe and you did not receive it from user side - then you just can use string interpolation #{1}. If not - you can use interpolation with sanitize that value #{ActiveRecord::Base.connection.quote(1)}
In my particular case I'm able to achieve this using arel.exists, rails will then prepare and bind with the OuterModel query.
inner_models = ::InnerModel.where("outer_model.id = inner_model.outer_model_id")
.where(paramter: 1)
::OuterModel.where(inner_models.arel.exists)
Although it doesn't really answer the in-general question on binding against raw SQL.

Reducing an if statement to generate a mongoid query

I have the code below and I would like to know if it possible to reduce the code.
bank_accounts = self.client_bank_account_id.nil? ? self.client.bank_accounts : self.client.bank_accounts.where(_id: self.client_bank_account_id)
I only need the where call when client_bank_account_id is not nil.
Not exactly shorter, but I'd say its more readable:
bank_accounts = self.client.bank_accounts
bank_accounts = bank_accounts.where(_id: self.client_bank_account_id) unless self.client_bank_account_id.nil?
That depends, are you going for 'shortest code possible' code golf style or simply the nicest, possibly easier reading code.
I'd go with:
bank_accounts = client.bank_accounts
bank_accounts = bank_accounts.where(_id: client_bank_account_id) if client_bank_account_id
Notes:
You don't need 'self'.
nil checks are probably not the best way to go.
Lazy loading should stop there being two calls to the db here

Remove a 'where' clause from an ActiveRecord::Relation

I have a class method on User, that returns applies a complicated select / join / order / limit to User, and returns the relation. It also applies a where(:admin => true) clause. Is it possible to remove this one particular where statement, if I have that relation object with me?
Something like
User.complex_stuff.without_where(:admin => true)
I know this is an old question, but since rails 4 now you can do this
User.complex_stuff.unscope(where: :admin)
This will remove the where admin part of the query, if you want to unscope the whole where part unconditinoally
User.complex_stuff.unscope(:where)
ps: thanks to #Samuel for pointing out my mistake
I haven't found a way to do this. The best solution is probably to restructure your existing complex_stuff method.
First, create a new method complex_stuff_without_admin that does everything complex_stuff does except for adding the where(:admin => true). Then rewrite the complex_stuff method to call User.complex_stuff_without_admin.where(:admin => true).
Basically, just approach it from the opposite side. Add where needed, rather than taking away where not needed.
This is an old question and this doesn't answer the question per say but rewhere is a thing that exists.
From the documentation:
Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
So something like:
Person.where(name: "John Smith", status: "live").rewhere(name: "DickieBoy")
Will output:
SELECT `people`.* FROM `people` WHERE `people`.`name` = 'DickieBoy' AND `people`.`status` = 'live';
The key point being that the name column has been overwritten, but the status column has stayed.
You could do something like this (where_values holds each where query; you'd have to tweak the SQL to match the exact output of :admin => true on your system). Keep in mind this will only work if you haven't actually executed the query yet (i.e. you haven't called .all on it, or used its results in a view):
#users = User.complex_stuff
#users.where_values.delete_if { |query| query.to_sql == "\"users\".\"admin\" = 't'" }
However, I'd strongly recommend using Emily's answer of restructuring the complex_stuff method instead.
I needed to do this (Remove a 'where' clause from an ActiveRecord::Relation which was being created by a scope) while joining two scopes, and did it like this: self.scope(from,to).values[:joins].
I wanted to join values from the two scopes that made up the 'joined_scope' without the 'where' clauses, so that I could add altered 'where' clauses separately (altered to use 'OR' instead of 'AND').
For me, this went in the joined scope, like so:
scope :joined_scope, -> (from, to) {
joins(self.first_scope(from,to).values[:joins])
.joins(self.other_scope(from,to).values[:joins])
.where(first_scope(from,to).ast.cores.last.wheres.inject{|ws, w| (ws &&= ws.and(w)) || w}
.or(other_scope(from,to).ast.cores.last.wheres.last))
}
Hope that helps someone

Ruby on Rails: is there a way to find in ActiveRecord similar to #notifications.delete_if{|x| x == cookie[0].to_i

I really like this syntax, though I forgot what it was called
#notifications.delete_if{|x| x == cookie[0].to_i
but I would like to do something similar for find
<% #managers = User.find{|x| x.isAdmin == true} %>
any ideas?
currently my solution tells me it can't find the object without an id, probably because I'm trying to treat active record like an array...
You could use #managers = User.find_all_by_isAdmin(true) or #managers = User.find(:all, :conditions => ['isAdmin = ?', true]).
Edit: To really be like what you have above, it might be something more along the lines of User.all.select { |x| x.isAdmin == true } but that seems a little weird as you'd be fetching everything from the User table, when you don't need to.
Since ActiveRecord is basically translating any find() requests into equivalent SQL queries, I think it would be difficult to ask it to translate an arbitrary condition stated in ruby code (x.isAdmin == true) into SQL.
So either you can use one of the more SQL-friendly approaches, such as User.find(:all, :conditions => 'isAdmin = true'), or you can pull everything from the database and then apply ruby-style filtering afterwards: User.find(:all).select(|x| x.isAdmin == true)
The latter approach is probably not a good idea in most cases, since all of the rows will have to be returned and then processed in-memory, but it can make sense where the selection criteria is complex and hard to translate into ActiveRecord queries or into SQL.
I don't think you should do this. As theIV said, it would be a lot better if you use something like
#managers = User.find_all_by_isAdmin(true)
There's no need to fetch everything from db if you won't use.

Allow the user to pick a named scope via GET params

In my posts model, I have a named scope:
named_scope :random, :order => "Random()"
I'd like to give users the ability to get posts in a random order by sending a GET request with params[:scope] = 'random'.
Short of eval("Post.#{params[:scope]}"), how can I do this?
I would suggest my very awesome acts_as_filter plugin designed for user-driven filtering of results via named_scopes.
http://github.com/tobyhede/acts_as_filter/tree/master
Eval is fine to use - but make sure you validate against accepted/expected values (I often just plug some values into an array and test accepted_values.include?(parameter))
eval is a pretty bad idea. However, #send is perfect for this - it's inherently safer, and faster than eval (as I understand it).
Product.send(params[:scope])
That should do it :)
I came across it in a search. searchlogic is perfect for this.
I would stay away from eval since you're dealing with data that comes from the user. Maybe just use a simple case statement? This way you'll be able to validate what the data they're giving you.
For the example you give, I'd be explicit, and chain scopes together to build the query you want:
scope = Post
scope = scope.random if params[:scope] == 'random'
#posts = scope.find(:all, ...) # or paginate or whatever you need to do
If params[:scope] isn't 'random', this is the same as calling Post.find(), otherwise it's doing Post.random.find()
From one of the other answers, it looks like find_by_filter would do pretty much the same thing for you.
Using this pattern, you can also combine multiple scopes into the query if you needed to support things that weren't mutually exclusive
e.g.
scope = scope.only_monsters if params[:just_monsters] == 1
scope = scope.limit(params[:limit].to_i) unless params[:limit].to_i.zero?
So GETting /posts?scope=random&just_monsters=1&limit=5 will give you:
Post.random.just_monsters.limit(5).find(:all, ...)

Resources