How to have a non query based scope in Rails - ruby-on-rails

How can I turn a filter on an array of ActiveRecord objects into a scope?
For example turn this:
Users.all.collect { |u| u.has_super_powers? }
Into:
scope :supers, #something here
The point is that it can be used as a scope so Users.all is not always "all" like:
Users.undeleted.supers.find_by_this('that')
I thought maybe using lambda expressions in scopes is the way, but don't think that would work since I don't have access to records as the expression is added to a DB query and not run as a post step over the results.

It depends wether or not you can transform u.has_super_powers? into a database query. Sometimes it is possible, sometimes its not.
For example: If you have a database field has_super_powers (boolean column) in the users table you can create a scope on that:
scope :has_super_powers, where(:has_super_powers => true)
Now you can chain it together with other scopes:
User.undeleted.has_super_powers.find_by_this('that')

Related

activerecord not like query

I could not find an activerecord equivalent of "Not Like". I was able to find a where.not, but that will check if a string does not match a value, as so:
User.where.not(name: 'Gabe')
is the same as:
User.where('name != ?', 'Gabe')
I was looking for a NOT LIKE, where the value is not contained in the string. The equivalent sql query would look like as follows:
SELECT * FROM users WHERE name NOT LIKE '%Gabe%'
In ActiveRecord I can currently get away with the following:
User.where("name NOT LIKE ?", "%Gabe%")
But that leaves a lot to be desired. Any new additions to Rails 4 to facilitate this?
Well, you can do something like:
User.where.not("name LIKE ?", "%Gabe%")
Note: This is only available in Rails 4.
As others have pointed out ActiveRecord does not have a nice syntax for building like statements. I would suggest using Arel as it makes the query less database platform specific (will use ilike for sqlite & like for other platforms).
User.where(User.arel_table[:name].does_not_match('%Gabe%'))
You could also implement this as a scope to contain the implementation to the model:
class User < ActiveRecord::Base
scope :not_matching,
-> (str) { where(arel_table[:name].does_not_match("%#{str}%")) }
end
Unfortunately ActiveRecord does not have a like query builder. I agree that the raw 'NOT LIKE' leaves a lot to be desired; you could make it a scope (scope :not_like, (column, val) -> { ... }), but AR itself does not do this.
Just addition to the answer of "where.not" of active record. "where.not" will exclude null values also. i.e. Query User.where.not(name: 'Gabe') will leave record with name 'Gabe' but it also exclude name column with NULL values. So in this scenario the solution would be
User.where.not(name: 'Gabe')
.or(User.where(name: nil))

Add conditions do activerecord includes

First I have this:
has_one :guess
scope :with_guesses, ->{ includes(:guess) }
Which loads all guesses (if they exists) for a 'X' model (run two queries). That's ok. works perfectly.
But I need to add one more condition to It.
If I do (my first thought):
scope :with_guesses, ->(user) { includes(:guess).where("guesses.user_id = ?", user.id) }
It will also run ok, BUT in one query (join) which will exclude results that doesn't have a 'guess'.
Any tips on how to use include with conditions but KEEPING the results that don't have a 'guess' ?
UPDATE
I ended up solving this by using a decorator, which I can pass the user as a context in the controller call, keeping the views clean.
I've used the Draper gem (https://github.com/drapergem/draper) to do this. You don't really need a gem to work with decorators in rails, but it can be helpful.
I didn't test it but you can use something like
User.eager_load(:guesses).where("guesses.user_id = ?", user.id)
when you using includes and where, the includes left join will be inner join.
so if you want to using a left join with where, you have to use string sql fragment:
scope :with_guesses, ->(user) { joins('left outer join guesses on guesses.user_id = ?',
user.id)}
I didn't test this code above, you have to test it yourself, this is just a way to think about
this problem.
here is reference:
http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations

Why is Rails 3 not excluding 'or'-ed condition when overriding scopes?

Consider these scopes on an ActiveRecord model:
scope :onsale, where(:sales_state => "onsale")
scope :outdated, where(:sales_state => "outdated")
scope :onsale_or_outdated, where(" sales_state = 'onsale' OR sales_state = 'outdated' ")
Why is it that Rails knows to override :onsale with :outdated, but not :onsale with :onsale_or_outdated?
My use case:
I have a relation object with many scopes (let's say it's a saved search), and one of those is :onsale. I want to make another relation starting from that, but this time I want the sales_state to be either onsale or outdated.
If I use relation.onsale_or_outdated, the sales_state is not overridden, it just adds a new condition.
[...] WHERE "cars"."sales_state" = 'onsale' [...] AND (("cars"."sales_state" = 'onsale' OR "cars"."sales_state" = 'outdated'))
How can I use my 'or'-ed condition in this context?
If I use relation.onsale_or_outdated, the sales_state is not overridden, it just adds a new condition.
That's how scopes work. They append, not replace. If you have two mutually exclusive scopes, you need to use one or the other, not both. The special case is when you have a scope involves a single field in :symbol = <value> syntax. Rails is smart enough to allow one scope to cancel the other out. In your case, the onsale_or_updated scope is simply a string, Rails has no means to tell which fields are involved and so the scopes are chained.
You should rewrite your scope to use fields/values instead of a blob of SQL, so Rails knows which fields are involved.
scope :onsale_or_outdated, where(:sales_state => %w(onsale outdated))
Alternatively, if you want to use only your onsale_or_outdated scope, you can unscope the relationship and reapply a scope:
relation.unscoped.onsale_or_outdated
Note that this will remove any previously applied scopes.

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

Empty Scope with Ruby on Rails

Following Problem:
I need something like an empty scope. Which means that this scope is emtpy, but responds to all methods a scope usually responds to.
I'm currently using a little dirty hack. I simply supply "1=0" as conditions. I find this realy ugly, since it hits the database. Simply returning an empty array won't work, since the result must respond to the scoped methods.
Is there a better existing solution for this or will I need to code this myself?
Maybe some example code could help explain what i need:
class User < ActiveRecord::Base
named_scope :admins, :conditions => {:admin => true }
named_scope :none_dirty, :conditions => "1=0" # this scope is always empty
def none_broken
[]
end
def self.sum_score # okay, a bit simple, but a method like this should work!
total = 0
self.all.each do |user|
total += user.score
end
return total
end
end
User.admin.sum_score # the score i want to know
User.none_drity.sum_score # works, but hits the db
User.none_broken.sum_score # ...error, since it doesn't respond to sum_score
Rails 4 introduces the none scope.
It is to be used in instances where you have a method which returns a relation, but there is a condition in which you do not want the database to be queried.
If you want a scope to return an unaltered scope use all:
No longer will a call to Model.all execute a query immediately and return an array of records. In Rails 4, calls to Model.all is equivalent to now deprecated Model.scoped. This means that more relations can be chained to Model.all and the result will be lazily evaluated.
User.where('false')
returns an ActiveRecord::Relation with zero elements, that is a chain-able scope that won't hit the database until you actually try to access one of its elements. This is similar to PhilT's solution with ('1=0') but a little more elegant.
Sorry User.scoped is not what you want. As commented this returns everything. Should have paid more attention to the question.
I've seen where('1 = 0') suggested before and Rails should probably cache it as well.
Also, where('1 = 0') won't hit the database until you do .all, .each, or one of the calculations methods.
I thing you need User.scoped({})
How about User.where(id: nil) ?
Or User.where(_id: nil) for mongoid.
The thing you are looking for does not exist. You could implement something like this by monky patching the find method. Yet, this would be an overkill, so I recomend keeping this unless it's performance critical.
Looking at your example code indicates you may not know about aggregated queries in SQL which are exposed as calculations methods in Rails:
User.sum(:score) will give you the sum of all users' scores
Take a look at Rails Guides for more info:
http://guides.rubyonrails.org/active_record_querying.html#sum

Resources