Remove a 'where' clause from an ActiveRecord::Relation - ruby-on-rails

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

Related

ActiveRecord - "includes" & "where" gettting Nil and Not Nil Statement, with other attributes

I want to efficiently get the results using '.includes()'.
I need the includes Table to retrieve the records that are NOT nil. I need to keep the included: true in the query.
Using postgres.
Role has many tasks.
Tasks belongs to a Role.
The following works but gets the wrong records from the Roles table.
Task.includes(:role).where(included: true, roles: {doer: nil})
The next part is the IDEA of what I want...
but obviously syntax is wrong in the where clause's "roles" value.
Task.includes(:role).where(
included: true,
roles: {
doer != nil #=> where 'doer' is not 'nil'
})
I'm looking for an efficient query which is why i did the includes.
I don't know how to get this without multiple where queries.
If you understand the question but think it could be asked better to be more clear, let me know. I couldn't any clues for this answer anywhere unless multiple where statments are used.
I prefer to avoid strings when possible, so I'd use the following:
Task.includes(:role).where(included: true).where.not(roles: { doer: nil })
If you have specifically set the included column on the tasks table to have NULL FALSE, you could also condense the where calls into a single call, although this will still only launch one query:
Task.includes(:role).where.not(included: false, roles: { doer: nil })
Personally, I'd like to see this cleaned-up a bit with some scopes, providing that these calls are commonplace. Something like:
scope :with_doer, -> { includes(:role).where.not(roles: { doer: nil }) }
so the resulting code would be more readable:
Task.with_doer.where(included: true)
You could obviously extend this pattern to the included: true bit as well.
Note that ActiveRecord queries are built up and then kicked to the database with a "kicker" which Rails kind of sneakily and hackily does through #inspect or #to_a most of the time. So, you don't need to worry about needing to condense the wherecalls into a single call.
What about something like:
Task.includes(:roles).where.not('roles.doer' => nil)
This is a rails 4-and-up convention, for rails 3 it would be something like:
Task.includes(:roles).where("roles.doer IS NOT NULL")
And you don't need the included attribute, it can be removed from the model for these purposes.
Since you do seem to need included (oops)
Task.includes(:roles).where('tasks.included' => true, 'roles.doer' => !nil)
Aaah.. how i love postgres.. When you're asking for efficiency, I think this fails, but I'm returning correct results. If you benchmark the options, this is correct (I think) but slow.
Task.joins('LEFT OUTER JOIN "roles" ON roles.user_id = user_id').where("tasks.included IS true AND roles.doer IS NOT NULL")
Write SQL statements like this?
Task.includes(:role).where("included = 'true' AND roles.doer NOT 'nil'")

Rails - why can't I use activerecord exists in my scope?

I'm trying to utilize .exists?() to return true or false, pretty simple. It would be nice to do the one-liner scope like so:
scope :any_alternates, lambda{|apikey| Track.exists?(:track_id => apikey)}
Or even using this scope syntax:
scope :any_alternates, ->(apikey) {Track.exists?(:track_id => apikey)}
But for some reason, the above scopes will return all rows in my db table when there's not a match. It works how it should when it finds a match however, but breaks if none...
I'm forced to create a method, which (to my knowledge) should be doing the same thing in the above scope:
def self.any_alternates(apikey)
return Track.exists?(:track_id => apikey)
end
Any idea why .exists?() isn't working inside of my scope?
After some testing...
If there is no match, then the scope will return all rows in the DB... (I updated above to mention that). I checked the generated query on both the scope and method to see if there's a difference, but they're the same:
SELECT 1 AS one FROM `tracks` WHERE `tracks`.`track_id` = '_btbd_uUmQT8hYUK3SrJ9Q' LIMIT 1
Update:
even though I'm searching on a column called track_id, this column is not setup as a relationship to another model. I know this is confusing, but that's how this table got setup (for good reason, beyond this issue so not worth touching on here)
Are you passing in nil? That would cause all records to be returned. You can drop the Track in the scope, like this:
scope :any_alternates, lambda{|apikey| exists?(:track_id => apikey)}
Here is what happens when you pass in nil:
> Track.any_alternates(nil).count
Track Exists (1.7ms) SELECT 1 AS one FROM "track" WHERE "tracks"."track_id" IS NULL LIMIT 1
instead of passing in a value:
> Track.any_alternates('X4DBA36gbtqgWl4F1')
Track Exists (0.4ms) SELECT 1 AS one FROM "tracks" WHERE "tracks"."track_id" = $1 LIMIT 1 [["track_id", "X4DBA36gbtqgWl4F1"]]
Are you sure that you have the right search? A Track having a track_id implies that there is a relation between Track and another track model which would be interesting.
Also the scope syntax is off. A scope is just a query of the table, so traditionally it is only things associated with the current model.
scope :any_alternates, ->(api_key) { |api_key| where(track_id: api_key }

How to get table column value?

I write follow code to get one record from the table webeehs:
webeehs_result = Webeeh.find(:all, :conditions=>["webeeh_project_id=#{project_id}"])
Then I want to get one column value from this record, how could I do?
For example, the column name is webeeh_date.
first of all, never EVER write code like that. Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. If you must do conditions, then do it like this:
:conditions => ["webeeh_project_id = ?", project_id]
if you have a Project model, you should rename the webeeh_project_id column from your Webeeh model into project_id and have an association in your Project model like has_many :webeehs
Then, you won't need to call that find anymore, just do a p = Project.find(id) and then p.webeehs will return the webeehs you need.
the result will be an array which you can iterate through. And to get your webeeh.webeeh_date member, just call it like this:
result.each do |webeeh|
date = webeeh.webeeh_date
end
webeehs_result = Webeeh.findwebeeh_dates
is enough to get all columnn values.
For a different method and performance issues check the following: http://www.stopdropandrew.com/2010/01/28/finding-ids-fast-with-active-record.html
webeeh_result will usually be an array of results for the database.
You can iterate throughit using
webeehs_result.each do |webeeh|
# use "webeeh.webeeh_date" to access the column_name or do whatever you want with it.
end

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

rails/activerecord search eager loaded associations

I have a simple find statement as such:
m = MyModel.find(1, :include => :my_children)
With m.mychildren being an Array; is there anyway to find a particular record from within the array without having to iterate over the entire thing. If I do mychildren.find(1), a new DB query is issues, which doesn't make sense, since they are all loaded already
It looks like there's a little Rails magic going on here. Where Enumerable#find is being overridden by ActiveRecord::Base#find on methods created for associations.
On the upside Enumerable#find is aliased to Enumerable#detect.
Unfortunately Enumerable#find/Enumerable#detect have significantly different syntax from ActiveRecord::Base#find.
So you can't just do mychildren.find(1), instead you've got to do mychildren.detect{|c| c.id == 1} if you want to avoid hitting the database again. You may also want to consider extending Array for a more DRY way of doing this.
class Array
def id_find id
self.detect{|element| element.id == id}
end
end
I'm not quite sure what your asking, but have you tried select:
m.mychildren.select{ |child| child == <<some_statement>> }
This won't hit the database assuming you've used the :include option as you stated in your question.
Alternatively, if you know the number of the child you want, you should be able to just use
m.mychildren[1]

Resources