Scope with join make SQL safe - ruby-on-rails

I have a scope for returning a list of projects a user has access too. Either they are on the participant list or they own the project they are listed. Query works fine except its not SQL safe. I can't figure out how to make the JOIN safe. The where clause is safe but trying the same with join doesn't work. I can't seem to find documentation or an answer here. Guessing I'm missing something basic.
scope :manageable_by_user, lambda { |user|
joins("LEFT JOIN participants ON
participants.project_id = projects.id
AND participants.user_id = #{user.id}").
where("projects.user_id = ? OR projects.user_id IS NOT NULL",user.id)
}

use ActiveRecord::Base.sanitize(string)

Related

Can I use an Arel created alias as part of includes rather than join for eager fetching

I have a self referencing relationship where an Employee will reference the same table as it's Supervisor. I need to query the table and be able to sort by "supervisor.first_name" which is technically, "employees.first_name". I was able to create a join using Arel however, I need it eager fetched so that I can use that alias in the order clause.
immediate_supervisor_alias = Employee.arel_table.alias(:supervisor)
supervisor_join = immediate_supervisor_alias.create_on(
Employee.arel_table[:id].eq(immediate_supervisor_alias[:id])
)
And then I use that like so...
#employees = Employee.joins(
Employee.arel_table.create_join(
immediate_supervisor_alias, supervisor_join, Arel::Nodes::OuterJoin
)
)
.includes(...)
.order('supervisor.first_name ASC')
This works, but fails to sort because the supervisor is lazy loaded. I need it to be an includes but I can't find anything about how to use an alias there. Arel API docs haven't been much help.
Well, I got it to work. I don't know if this is the best way, but it does work, so calling it good unless someone comes by with a better answer.
#employees = Employee.joins(
Employee.arel_table.create_join(
immediate_supervisor_alias, supervisor_join, Arel::Nodes::OuterJoin
)
)
.select('supervisor.id, supervisor.last_name, supervisor.first_name')
.includes(...)
.order('supervisor.first_name ASC')
Note the addition of the select.

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

Rails can't select where foreign key is nil

I have a rails app with a Customer and a ShippingAddress model. It is implemented with a one-to-many relationship so that a ShippingAddress can have multiple customers.
I am successfully able to query across these two models and several others with an include statement, but as I tried to update the query to find all of the customers that does not have a shipping_address got 0 results, even though I am able to se from my DB-admin tool that I have multiple customers where the value of the shipping_address_id is nil.
These queries works, but does not give me customers without addresses:
Customer.includes(:orders, :shipping_address).where('customers.name LIKE ? or customers.mail LIKE ?', searchstring, searchstring)
Customer.where('customers.shipping_address_id = ?',2)
These attempts to adapt the above to give me customers without addreses doesn't:
Customer.includes(:orders, :shipping_address).where('shipping_address = ?', nil)
Customer.includes(:orders, :shipping_address).where('shipping_address = NULL')
# At the very least I should be able to get the right result by referencing the fk directly, but no?
Customer.where('customers.shipping_address_id = ?',nil)
What am I missing here?
The NULL value can be surprising until you get used to it. Conceptually, NULL means “a missing unknown value” and it is treated somewhat differently from other values.
You cannot compare null using equal to for this you must use IS NULL. So update your queries to
Customer.includes(:orders, :shipping_address).where('customers.shipping_address_id IS NULL')
Or rails way of doing this is
Customer.where(shipping_address_id: nil).includes(:orders, :shipping_address)
You could also just do:
#customers_without_shipping_ids = Customer.where('shipping_address_id IS NULL').all
Please have a try with these queries
Customer.all(:conditions => ['shipping_address_id IS NULL'])
and
Customer.includes(:orders, :shipping_address).where('shipping_address_id IS NULL')

Rails 3: How to eager load an association AND apply a condition with includes()?

My app has Question models that has_many UserResponse models.
I'd like to get the last 5 questions that a particular User has answered with the associated UserResponse objects filtered on the user_id field.
Here's what I currently have:
Question.group("questions.id").
joins("inner join user_responses r on r.question_id = questions.id").
where("r.user_id = #{user_id}").
order("questions.id asc").limit(5)
This query gives me back what I want, but the problem is when I get a Question out of the array and do question.user_responses the result is all of the responses for that question, not just the ones that should be filtered by the join/where clause above.
I've tried to do this:
Question.includes(:user_responses).group("questions.id").
joins("inner join user_responses r on r.question_id = questions.id").
where("r.user_id = #{user_id}").
order("questions.id asc").limit(5)
thinking it would eager load each response...but it doesn't appear to function that way.
Where am I going wrong?
If it makes a difference, the reason I need everything eagerly loaded is because I want to take the array and call to_json on it and return the json from a web service for my mobile app, so I need the entire graph available.
I think you're trying to get too complicated here. How about the following?
Question
.includes(:user_responses)
.where("user_id = ?", user_id)
.order("questions.id asc")
.limit(5)

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

Resources