Relation passed to #or must be structurally compatible. Incompatible values: [:references] - ruby-on-rails

I have two queries, I need an or between them, i.e. I want results that are returned by either the first or the second query.
First query is a simple where() which gets all available items.
#items = #items.where(available: true)
Second includes a join() and gives the current user's items.
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
I tried to combine these with Rails' or() method in various forms, including:
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
.or(
#items
.joins(:orders)
.where(available: true)
)
But I keep running into this error and I'm not sure how to fix it.
Relation passed to #or must be structurally compatible. Incompatible values: [:references]

There is a known issue about it on Github.
According to this comment you might want to override the structurally_incompatible_values_for_or to overcome the issue:
def structurally_incompatible_values_for_or(other)
Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
(Relation::MULTI_VALUE_METHODS - [:eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
(Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
Also there is always an option to use SQL:
#items
.joins(:orders)
.where("orders.user_id = ? OR items.available = true", current_user.id)

You can write the query in this good old way to avoid error
#items = #items.joins(:orders).where("items.available = ? OR orders.user_id = ?", true, current_user.id)
Hope that helps!

Hacky workaround: do all your .joins after the .or. This hides the offending .joins from the checker. That is, convert the code in the original question to...
#items =
#items
.where(orders: { user_id: current_user.id})
.or(
#items
.where(available: true)
)
.joins(:orders) # sneaky, but works! 😈
More generally, the following two lines will both fail
A.joins(:b).where(bs: b_query).or(A.where(query)) # error! 😞
A.where(query).or(A.joins(:b).where(bs: b_query)) # error! 😞
but rearrange as follows, and you can evade the checker:
A.where(query).or(A.where(bs: b_query)).joins(:b) # works 😈
This works because all the checking happens inside the .or() method. It's blissfully unaware of shennanigans on its downstream results.
One downside of course is it doesn't read as nicely.

I ran into the same issue, however the code was defined in a different place and was very difficult to change directly.
# I can't change "p"
p = Post.where('1 = 1').distinct # this could also be a join
And I needed to add an or statement to it
p.or(Post.where('2 = 2'))
The following code won't raise an error, because it has distinct like the initial relationship.
p.or(Post.where('2 = 2').distinct)
The problem with it it that it only works as long as you know the relationship. It may or not have a join, or distinct.
This works regardless of what the relationship is:
p.or(p.unscope(:where).where('2 = 2'))
=> SELECT DISTINCT `posts`.* FROM `posts` WHERE ((1 = 1) OR (2 = 2))

It occurs when you try to combine two multi-active records of the same type, but one of them has a joins value or an includes value, or in your case a reference 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.
items_1 = #items.joins(:orders)
.where(orders: { user_id: current_user.id})
items_2 = #items.where(available: true)
.joins(items_1.joins_values)
.includes(items_1.includes_values)
.references(items_1.references_values)
#items = items_1.or(items_2)

just solve it!
def exec_or_statement(q1, q2)
klass = q1.klass
key = klass.primary_key
query_wrapper_1 = {}
query_wrapper_1[key] = q1
query_wrapper_2 = {}
query_wrapper_2[key] = q2
klass.where(query_wrapper_1).or(klass.where(query_wrapper_2))
end
query_1 = #items.where(available: true)
query_2 =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
exec_or_statement(query_1, query_2)

Related

Combine two ActiveRecord results and sort by a shared joined table attribute

I have a Convo table and a GroupMeeting table that both are associated with a Msg table.
I want to find all the instances where the current_user has convos or group_meetings with msgs, combine the two, and then show both together to the user in order of the last msg.created_at
Here I have defined both:
#convos = Convo.includes(:msgs).where("sender_id = ? OR recipient_id = ?", current_user, current_user).where.not(:msgs => { :id => nil }).merge(Msg.order(created_at: :desc))
#group_meetings = current_user.group_meetings.includes(:msgs).where.not(:msgs => { :id => nil }).merge(Msg.order(created_at: :desc))
And then combined them together:
#convos = #convos + #group_meetings
What I can't figure out is how to now sort them by msg.created_at
I have tried the following:
#convos = (#convos + #group_meetings).sort_by(&:"#{msg.created_at}")
#convos.order('msg.created_at DESC')
These all seem to be server-side sorting though. How can I sort these based off the join table, after the array has been created?
Please let me know if I need to supply any other details. Thank you!!
You can try the following:
(#convos + #group_meetings).sort_by { |item| item.msgs.minimum(:created_at) }

Rails subquery always returns nil value

I need to count rows from other table in subselect, so I use this query:
follows_sql = Follow.where(followable_type: 'Idea').where('follows.followable_id = ideas.id').select('COUNT(followable_id)').to_sql
idea = Idea.select("(#{follows_sql}) AS fcnt").includes(:collaborations).
where(collaborations: { user_id: 4, owner: true })
So its produced valid SQL, but I cant access 'fcnt' value from idea var. I tried do it in different ways like:
idea[0].fcnt # return nil
idea[0]["fcnt"] # return nil
But I only can access fields which exists in a Idea model.
How I can access my custom 'fcnt' field?
I think something along the following should work for you
idea = Idea.select("ideas.*, COUNT(follows.id) AS fcnt").joins("LEFT OUTER JOIN follows ON follows.followable_id = ideas.id").group("ideas.id")
ideas.each do |idea|
puts idea.fcnt
# Should output a number
end
Note that I've left out the other includes and where clauses. Try to solve the problem first, and if this query works out, then add in your additional clauses.
Also, if you setup your relations correctly, such that an idea has many follows, you could clean up your code by doing something like
ideas = Idea.includes(:collaborations).where(collaborations: { user_id: 4, owner: true })
ideas.map { |idea| idea.follows.count }

Rails: each for multiple and one object data

I'm new in rails and need to clear one question:
for example my method return such data:
#<Article ART_ID: 1151754, ART_ARTICLE_NR: "0 281 002 757", ART_SUP_ID: 30, ART_DES_ID: nil, ART_COMPLETE_DES_ID: 62395, ART_CTM: nil, ART_PACK_SELFSERVICE: 0, ART_MATERIAL_MARK: 0, ART_REPLACEMENT: 0, ART_ACCESSORY: 0, ART_BATCH_SIZE1: nil, ART_BATCH_SIZE2: nil, datetime_of_update: "2012-09-25 17:49:18">
or array, not only one object: how could use each func then?
for example:
articles = ArtLookup.search_strong_any_kind_without_brand(params[:article_nr].gsub(/[^0-9A-Za-z]/, ''))
binding.pry
if articles.present?
articles.each do |a|
#all_parts_result <<
{
analogue_manufacturer_name: a.supplier.SUP_BRAND,
analogue_code: a.ART_ARTICLE_NR,
delivery_time_min: '',
delivery_time_max: '',
min_quantity: '',
product_name: a.art_name,
quantity: '',
price: '',
distributor_id: '',
link_to_tecdoc: a.ART_ID
}
end
end
now i get errors like
`undefined method `each' for `#<Article:0x007f6554701640>
i think it is becouse i have sometimes one object, sometimes 10, and sometime 0.
how is it beatifull and right to do in rails?
Your search_strong_any_kind_without_brand method is looping through your articles based on the search condition. If the article matches then you are setting #art_concret to the match and then returning the match. However, you're not finding all matches, just the last one.
.
loop
#art_concret = art
end
.
return #art_concret
If you set the #art_concret as an array and inject results into this instance variable, then you will have the resulting search in array form. However, keep in mind that this does kind of break the ActiveRecord ORM as you would be returning a simple array and not an ActiveRecord Relation array.
def self.search_strong_any_kind_without_brand(search)
search_condition = search.upcase
#art_concret = []
#search = find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)', search_condition])
#articles = Article.find(:all, :conditions => ["ART_ID in (?)", #search.map(&:ARL_ART_ID)])
#binding.pry
#articles.each do |art|
if art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search
#art_concret << art
end
end
return #art_concret
end
If you want to keep the code a bit cleaner then use select on your matching condition instead of looping through each article in #articles.
def self.search_strong_any_kind_without_brand(search)
search_condition = search.upcase
#search = find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)', search_condition])
#articles = Article.find(:all, :conditions => ["ART_ID in (?)", #search.map(&:ARL_ART_ID)])
#binding.pry
return #articles.select { |art| art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search }
end
Unrelated: is there a reason why you're using instance variables in search_strong_any_kind_without_brand?
I think the right thing to do is to make sure your method always returns an array (or enumerable).
looking at the code you posted in to pastebin I would recommend you use Array#select in your method
for example you might be able to just return this:
#articles.select { |art| art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search }
assuming #articles is an array or collection you will always get an array back, even if it is 0, or 1 element
This answer would be a bit offtopic, but I would like to mention a splat operator:
[*val]
will produce array, consisting of either single val value whether it’s not an array, or the array itself whether val is an array:
β–Ά def array_or_single param
β–· [*param].reduce &:+ # HERE WE GO
β–· end
=> :array_or_single
β–Ά array_or_single [1,2,3]
=> 6
β–Ά array_or_single 5
=> 5
That said, you code would work with this tiny improvement:
- articles.each do |a|
+ [*articles].each do |a|
Hope it gives a hint on how one might handle the data, coming from the 3rd party. As an answer to your particular question, please follow the advises in the other answers here.

operator in rails

What is the effective way of this code?I think there should be better way.I wanna re-code this.
if #project.contest_entries.where("view_in_showcase = ?", true)
entries = #project.contest_entries.where("view_in_showcase = ?", true).count
else
entries = 1
end
You could use max:
entries = [1, #project.contest_entries.where(view_in_showcase: true).count].max
I would define a scope on ContestEntry to get rid of that where clause though:
scope :showcased, where(view_in_showcase: true)
Then that would become
entries = [1, #project.contest_entries.showcased.count].max
showcased_project_entries =
#project.contest_entries.where("view_in_showcase = ?", true)
entries = showcased_project_entries ? showcased_project_entries.count : 1
or
entries =
#project.contest_entries.where("view_in_showcase = ?", true).try(:count) || 1
Although, I must admit I am not sure under which circumstances where returns a falsy value.
EDIT: As noted in the comments, the else clause indeed never triggers, so your code probably does not do what you want. See Andy H's solution for the case where you want to have entries be 1 when you find no results, if that is what you meant.

retrieve data from database - hash

I have a table called audits which has a column 'changes' storing data in the form of hash
I would like to retrieve all entries with the following conditions:
- auditable_type = 'Expression'
- action = 'destroy'
- changes = { :EXP_SUBMISSION_FK =>'9999992642'}
I first tried the following code which returns me with nothing:
#deleted_history = Audit.find(:all, :conditions => ["auditable_type =? AND action = ? AND changes = ?",'Expression', 'destroy' , { :EXP_SUBMISSION_FK =>'9999992642'} ])
I then tried the following code which retrieves all entries in the 'audits' table with auditable_type = 'Expression' and action = 'destroy'.
I then loop through the resultset and discards all entries where EXP_SUBMISSION_FK is not equal to 9999992642. The code below returns me 5 entries/records
#deleted_history = Audit.find(:all, :conditions => ["auditable_type =? AND action = ?",'Expression', 'destroy' ])
#deleted_history.each do |test|
if test.changes['EXP_SUBMISSION_FK'] != 9999992642
#deleted_history = #deleted_history.reject { test }
end
end
I would like to know where did I go wrong with the first code example and whether there is a way of retrieving all entries with the aforementioned conditions in a much simpler way.
Thanks a lot for your help.
i'd do:
#deleted_history.select!{|hist| hist.changes['EXP_SUBMISSION_FK'] == '9999992642'}
One potential cause of failure is that you're looking for 9999992642 but you state before the value is '9999992642'
You just use something like below. I am storing element_values as a hash and i am selecting records based on the key/value pair.
scope :find_by_field_values, lambda {
|field_name, field_value|
(where("element_values like ?", "%\"#{field_name}\":\"%#{field_value}%"))
}
just try this based on your scenario.

Resources