Relation passed to #or must be structurally compatible. Incompatible values: [:joins] - sql

I have this error message
Relation passed to #or must be structurally compatible. Incompatible values: [:joins]
in my user model: has_many :orders
in my order model: belongs_to :user, optional: true
How I am supposed to write my query to have either the users' names and the order id in the same search input?
def filter_orders
return if params[:query].blank?
#orders = Order.joins(:user).where('lower(users.first_name) LIKE ?', "%#{params[:query][:keyword]}%")
.or(Order.joins(:user).where('lower(users.last_name) LIKE ?', "%#{params[:query][:keyword]}%"))
.or(Order.where(id: "#{params[:query][:keyword]}.to_i"))
end

It sound like this is a know issue with .or. Try using SQL or you can override .or as seen in this answer: https://stackoverflow.com/a/40742512/10987825

It occurs when you try to combine two multi-active records of the same type, but one of them has a reference value or an includes value, or in your case a joins value, that the other does not.
Therefore we need to match the values between them, and I found a general way to do this without knowing the actual values in advance.
def filter_orders
return if params[:query].blank?
orders_1 = Order.joins(:user).where('lower(users.first_name) LIKE ?', "%#{params[:query][:keyword]}%")
orders_2 = Order.joins(:user).where('lower(users.last_name) LIKE ?', "%#{params[:query][:keyword]}%")
orders_3 = Order.where(id: "#{params[:query][:keyword]}.to_i")
joind_orders = orders_1.or(
orders_2
.joins(orders_1.joins_values)
.includes(orders_1.includes_values)
.references(orders_1.references_values)
)
#orders = joind_orders.or(
orders_3
.joins(joind_orders.joins_values)
.includes(joind_orders.includes_values)
.references(joind_orders.references_values)
)
end

Related

How to write this SQL using Rails' read methods?

I have the following setup:
question.rb
class Question < ActiveRecord::Base
has_many :answers
#validations, methods, etc
...
#Returns the questions with the most answers
def Question.top_questions(max = 10)
sql = "SELECT question_id, COUNT('question_id') as aCount FROM answers GROUP BY question_id ORDER BY aCount DESC LIMIT #{max.to_i}" # Probably shouldn't use string interpolation here :D
Question.connection.execute(sql)
end
end
answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
...
end
And if I call Question.top_questions(), then it returns this:
[{"question_id"=>1, "aCount"=>25, 0=>1, 1=>25}, {"question_id"=>38, "aCount"=>3, 0=>38, 1=>3}, {"question_id"=>45, "aCount"=>3, 0=>45, 1=>3}, {"question_id"=>26, "aCount"=>2, 0=>26, 1=>2}, {"question_id"=>46, "aCount"=>2, 0=>46, 1=>2}, {"question_id"=>48, "aCount"=>2, 0=>48, 1=>2}, {"question_id"=>51, "aCount"=>2, 0=>51, 1=>2}, {"question_id"=>5, "aCount"=>1, 0=>5, 1=>1}, {"question_id"=>15, "aCount"=>1, 0=>15, 1=>1}, {"question_id"=>20, "aCount"=>1, 0=>20, 1=>1}]
I'm not sure how I would use the data returned in a view while still keeping the code clean.
So I'm wondering if I could write the Question.top_questions() method using rails' read methods(find(), where(), etc). Or how I could get it to return an array of Question objects.
It returns an array of hashes, you could use it in a view as you like.
But if you don't want to write native sql, you could rewrite it as below.
def self.top_questions(max = 10)
Question.joins('LEFT JOIN answers ON questions.id = answers.question_id')
.select('questions.*, count(answers.id) as answers_count')
.group('questions.id')
.order('answers_count desc')
.limit(max)
end

Postgres full text search using pg_search - includes(:child_model) breaks the SQL with 'missing FROM-clause entry for table 'child_model'

I'm using postgres full text search with the pg_search gem. The search itself is working well, but I need to further filter the results and here are the details:
class Notebook < ActiveRecord::Base
has_many :invites
def self.text_search(query)
if query.present?
search(query)
else
scoped
end
end
Notebooks Controller:
def index
if params[:query].present?
#notebooks = Notebook.text_search(params[:query]).includes(:invites).where("invites.email = :email OR notebooks.access = :access OR notebooks.access = :caccess OR notebooks.user_id = :uid", email: current_user.email, access: "open", caccess: "closed", uid: current_user.id)
else
#notebooks = Notebook.includes(:invites).where("invites.email = :email OR notebooks.access = :access OR notebooks.access = :caccess OR notebooks.user_id = :uid", email: current_user.email, access: "open", caccess: "closed", uid: current_user.id)
end
The error I get is 'missing FROM-clause entry for table 'invites'. I have tried many different things including:
replacing 'includes' with 'joins'
replacing 'includes(:invites) with joins('LEFT JOIN "invites" ON "invites"."email" = "email" ')
changing the order of the .text_search and the .includes calls.
adding the includes call in the controller, in the model, in a scope, and in the text_search function definition.
I keep getting the same error, and when using the joins call with SQL it does not filter by invite emails, and shows multiple repeats of each search result.
I would just remove the include(:invites) because the text_search itself is working just fine. But I really need this condition to be included.
Any help would be greatly appreciated. Maybe I'm just getting my SQL call wrong, but I also would like to understand why the .includes(:invites) works without the pg text_search but won't work with it.
Edit #1 - more specific question
I think there are 2 slightly different questions here. The first seems to be some issue with combining pg_search gem and an 'includes(:invites)' call. The second question is what is the equivalent SQL statement that I can use in order to avoid making the 'includes(:invites)' call. I think it should be a LEFT JOIN of some sort, but I don't think I'm making it correctly. In my db, a Notebook has_many invites, and invites have an attribute 'email'. I need the the notebooks with invites that have an email equal to the current_user's email.
Help with either of these would be great.
Here is the link that showed me the solution to my problem:
https://github.com/Casecommons/pg_search/issues/109
Here is my specific code:
class Notebook < ActiveRecord::Base
has_many :invites
include PgSearch
pg_search_scope :search, against: [:title],
using: {tsearch: {dictionary: "english"}},
associated_against: {user: :name, notes:[:title, :content]}
scope :with_invites_and_access, lambda{ |c_user_email|
joins('LEFT OUTER JOIN invites ON invites.notebook_id = notebooks.id').where('invites.email = ? OR notebooks.access = ? OR notebooks.access = ?', c_user_email, 'open', 'closed')
}
def self.text_search(query)
if query.present?
search(query).with_invites_and_access(current_user_email)
else
scoped
end
end
end
The key was the joins statement. joins(:invites) doesn't work, includes(:invites) doesn't work. The full SQL statement is required:
joins('LEFT OUTER JOIN invites ON invites.notebook_id = notebooks.id')
I can see a join but I cannot see what makes joined invites fields to appear in the SELECT statement.
I think You may need to add the fields from the invites table into select() like this
select('invites.*').joins('LEFT OUTER JOIN invites ON invites.notebook_id = notebooks.id').where('invites.email = ? OR notebooks.access = ? OR notebooks.access = ?', c_user_email, 'open', 'closed')
}

Find all matching criteria where conditions not in array

I am trying to return a variable with values of an array of users. There are a few conditions that must be met. The user must have public_find set to true, it must not be the current_user (session variable of the user currently logged in), and it must not already be part of a friendship. The first and second conditions work perfectly. However, I am having an issue with the third part where the current_users.friendships needs to be an array of the ID values of the users where the association already exists. Any thoughts?
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id, current_user.friendships])
Edit:
I've figured out that I was missing pluck from the list. This works good now. However, if someone does not yet have a friend then current_user.friendships.pluck(:friend_id) will return NULL. I know that it is bad practice and unexpected results returned when using NOT IN and NULL. However, how do you create a condition where you can set the value to something realistic like [0] or [1] if the array returned is empty?
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id, current_user.friendships.pluck(:friend_id) || [0]])
EDIT AGAIN:
I got it working. However, now I want to know if this is best practice to have a statement like this. It basically is doing a check to see if the current_user.friendships.pluck(:friend_id) is empty or not. If it is then return [0]. Otherwise return an array of the user ids (foreign keyed as friend_id).
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id,
(current_user.friendships.pluck(:friend_id).empty? ? [0] : current_user.friendships.pluck(:friend_id))])
You can write this a little nicer ..
Show all users where public_find is true and also exclude the currently logged in user or any of their friends
ids = current_user.friendships.map(&:friend_id).concat([current_user.id])
#users = User.where(:public_find => true).where('id not in ?', ids)
I would use an arel table for this (which guarantees the code will work on any database):
t, f = User.arel_table, current_user.friendships
query = t[:public_find].eq(true).and(t[:id].not_eq(current_user.id))
query = query.and(t[:id].not_in(f.pluck(:friend_id))) unless f.empty?
#users = User.where(query)
Generated SQL for current_user = 3 and a single friendship with a user with id = 1:
SELECT "users".* FROM "users"
WHERE ("users"."public_find" = 't' AND "users"."id" != 3 AND "users"."id" NOT IN (1))
If current_user.friendships is nil, the unless f.empty? clause will prevent that condition from being applied at all, so it will not appear in the SQL:
SELECT "users".* FROM "users"
WHERE ("users"."public_find" = 't' AND "users"."id" != 3)
Also, note that because this code uses where instead of find, the final result is an ActiveRecord::Relation rather than an array of results. This means that you can further chain conditions onto it, e.g. to order the results by updated_at, change the last line to:
#users = User.where(query).order(:created_at)

SQL select distinct value

I'm trying to select the following data with the limited information. The problem is that when I have added the .select distinct section it has killed my query.
#activities = Availability.select.("DISTINCT user_id").where("team_id = ? and schedule_id = ?", current_user[:team_id], #next_game).last(5)
There's one too many dot's in there as the 'DISTINCT user_id' is the arguments for the select method call.
So:
Availability.select("DISTINCT user_id").where("team_id = ? and schedule_id = ?", current_user[:team_id], #next_game).last(5)
Also be aware that you're now only selecting one attribute and you'll get a partial representation of the classes back. To circumvent this just select the attributes you need later in the code.
Availability.select("DISTINCT(`user_id`), `team_id`").where("team_id = ? and schedule_id = ?", current_user[:team_id], #next_game).last(5)
etc.
Hope this helps.

How to specify multiple values in where with AR query interface in rails3

Per section 2.2 of rails guide on Active Record query interface here:
which seems to indicate that I can pass a string specifying the condition(s), then an array of values that should be substituted at some point while the arel is being built. So I've got a statement that generates my conditions string, which can be a varying number of attributes chained together with either AND or OR between them, and I pass in an array as the second arg to the where method, and I get:
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 5)
which leads me to believe I'm doing this incorrectly. However, I'm not finding anything on how to do it correctly. To restate the problem another way, I need to pass in a string to the where method such as "table.attribute = ? AND table.attribute1 = ? OR table.attribute1 = ?" with an unknown number of these conditions anded or ored together, and then pass something, what I thought would be an array as the second argument that would be used to substitute the values in the first argument conditions string. Is this the correct approach, or, I'm just missing some other huge concept somewhere and I'm coming at this all wrong? I'd think that somehow, this has to be possible, short of just generating a raw sql string.
This is actually pretty simple:
Model.where(attribute: [value1,value2])
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
Have a look at http://guides.rubyonrails.org/active_record_querying.html#array-conditions for more details on how this works.
Instead of passing the same parameter multiple times to where() like this
User.where(
"first_name like ? or last_name like ? or city like ?",
"%#{search}%", "%#{search}%", "%#{search}%"
)
you can easily provide a hash
User.where(
"first_name like :search or last_name like :search or city like :search",
{search: "%#{search}%"}
)
that makes your query much more readable for long argument lists.
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
#notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value) Have a
look at
http://guides.rubyonrails.org/active_record_querying.html#array-conditions
for more details on how this works.
Was really close. You can turn an array into a list of arguments with *my_list.
Model.where("id = ? OR id = ?", *["1", "2"])
OR
params = ["1", "2"]
Model.where("id = ? OR id = ?", *params)
Should work
If you want to chain together an open-ended list of conditions (attribute names and values), I would suggest using an arel table.
It's a bit hard to give specifics since your question is so vague, so I'll just explain how to do this for a simple case of a Post model and a few attributes, say title, summary, and user_id (i.e. a user has_many posts).
First, get the arel table for the model:
table = Post.arel_table
Then, start building your predicate (which you will eventually use to create an SQL query):
relation = table[:title].eq("Foo")
relation = relation.or(table[:summary].eq("A post about foo"))
relation = relation.and(table[:user_id].eq(5))
Here, table[:title], table[:summary] and table[:user_id] are representations of columns in the posts table. When you call table[:title].eq("Foo"), you are creating a predicate, roughly equivalent to a find condition (get all rows whose title column equals "Foo"). These predicates can be chained together with and and or.
When your aggregate predicate is ready, you can get the result with:
Post.where(relation)
which will generate the SQL:
SELECT "posts".* FROM "posts"
WHERE (("posts"."title" = "Foo" OR "posts"."summary" = "A post about foo")
AND "posts"."user_id" = 5)
This will get you all posts that have either the title "Foo" or the summary "A post about foo", and which belong to a user with id 5.
Notice the way arel predicates can be endlessly chained together to create more and more complex queries. This means that if you have (say) a hash of attribute/value pairs, and some way of knowing whether to use AND or OR on each of them, you can loop through them one by one and build up your condition:
relation = table[:title].eq("Foo")
hash.each do |attr, value|
relation = relation.and(table[attr].eq(value))
# or relation = relation.or(table[attr].eq(value)) for an OR predicate
end
Post.where(relation)
Aside from the ease of chaining conditions, another advantage of arel tables is that they are independent of database, so you don't have to worry whether your MySQL query will work in PostgreSQL, etc.
Here's a Railscast with more on arel: http://railscasts.com/episodes/215-advanced-queries-in-rails-3?view=asciicast
Hope that helps.
You can use a hash rather than a string. Build up a hash with however many conditions and corresponding values you are going to have and put it into the first argument of the where method.
WRONG
This is what I used to do for some reason.
keys = params[:search].split(',').map!(&:downcase)
# keys are now ['brooklyn', 'queens']
query = 'lower(city) LIKE ?'
if keys.size > 1
# I need something like this depending on number of keys
# 'lower(city) LIKE ? OR lower(city) LIKE ? OR lower(city) LIKE ?'
query_array = []
keys.size.times { query_array << query }
#['lower(city) LIKE ?','lower(city) LIKE ?']
query = query_array.join(' OR ')
# which gives me 'lower(city) LIKE ? OR lower(city) LIKE ?'
end
# now I can query my model
# if keys size is one then keys are just 'brooklyn',
# in this case it is 'brooklyn', 'queens'
# #posts = Post.where('lower(city) LIKE ? OR lower(city) LIKE ?','brooklyn', 'queens' )
#posts = Post.where(query, *keys )
now however - yes - it's very simple. as nfriend21 mentioned
Model.where(attribute: [value1,value2])
does the same thing