Rails join with where using AND instead of OR - ruby-on-rails

I have a scope (where title is an array)
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
By default this does an AND query. How can I get this to do an OR query? I can't seem to find any examples of this.
I believe this is the SQL that is generated:
Label Load (0.1ms) SELECT "labels".* FROM "labels" WHERE "labels"."project_id" = $1 AND "labels"."title" = $2 ORDER BY "labels"."title" ASC LIMIT 1 [["project_id", 8], ["title", "label1,label2,label3"]]

Rails/ActiveRecord only supports OR queries in Rails 5. In earlier versions, you need to use a SQL string to achieve an OR query:
Widget.where("name = ? OR name = ?", "foo", "bar") # postgres
When there are many possibilities, the same thing can be accomplished more efficiently with an IN query, which is generated in ActiveRecord by passing an array with more than one element to the #where method:
arr = ["foo", "bar", "baz"]
Widget.where(name: arr)
As for the SQL output you've posted:
Label Load (0.1ms) SELECT "labels".* FROM "labels" WHERE "labels"."project_id" = $1 AND "labels"."title" = $2 ORDER BY "labels"."title" ASC LIMIT 1 [["project_id", 8], ["title", "label1,label2,label3"]]
The AND here is coming from the fact that a query is being made against the product_id as well as the title, not because of anything to do with title.

Related

Different result in rails console and in puts

When I use puts(#participantt = Participant.where(id: 1)) then in the console I get
Participant Load (0.3ms) SELECT "participants".* FROM "participants" WHERE "participants"."id" = $1 [["id", 1]]
↳ app/controllers/interviews_controller.rb:119:in `puts'
#<Participant:0x000000000c778bf0>
But if I type #participantt = Participant.where(id: 1) in rails console then I get
Participant Load (0.7ms) SELECT "participants".* FROM "participants" WHERE "participants"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Participant id: 1, name: "Ram", email: "Ram#g.com", created_at: "2020-05-08 08:19:00", updated_at: "2020-05-08 08:19:00">]>
Why is this so?
puts calls to_s on before printing result of expression(which will be an object) passed to it. It generally prints class name with it's object id.
Here, result of #participant = Participant.where(id: 1) is the Participant with id and get stored in instance variable #participant
Passing #participant to puts will first call #participant.to_s before printing it.
In case of just #participant = Participant.where(id: 1), the console shows the result which is typical REPL utility does. And if you do puts #participant there after then you will again get same as what you get with puts in you question.

Build triple UNION query using Arel (Rails 5)

user has_many tasks
I'm trying to create a 3 select UNION:
Task.from("(#{select1.to_sql} UNION #{select2.to_sql} UNION #{select3.to_sql}) AS tasks")
But with Arel.
I can easily use Arel for UNION query for 2 selects:
tasks_t = Task.arel_table
select1 = tasks_t.project('id').where(tasks_t[:id].eq(1)) # SELECT id FROM tasks WHERE tasks.id = 1
select2 = Task.joins(:user).select(:id).where(users: {id: 15}).arel # SELECT "tasks".id FROM "tasks" INNER JOIN "users" ON "users"."id" = "tasks"."user_id" WHERE "users"."id" = 15
union = select1.union(select2) # SELECT * FROM users WHERE users.id = 1 UNION SELECT * FROM users WHERE users.id = 2
union_with_table_alias = tasks_t.create_table_alias(union, tasks_t.name)
Task.from(union_with_table_alias)
# SELECT "tasks".* FROM ( SELECT id FROM "tasks" WHERE "tasks"."id" = 1 UNION SELECT "tasks"."id" FROM "tasks" INNER JOIN "users" ON "users"."id" = "tasks"."user_id" WHERE "users"."id" = $1 ) "tasks" [["id", 15]]
#=> Task::ActiveRecord_Relation [#<Task: id: 1>, #<Task: id: 2>]
How can I do it with triple union select?
select3 = tasks_t.project('id').where(tasks_t[:id].eq(3)) # SELECT id FROM tasks WHERE tasks.id = 3
Something like:
triple_union = select1.union(select2).union(select3)
triple_union_with_table_alias = tasks_t.create_table_alias(triple_union, tasks_t.name)
Task.from(triple_union_with_table_alias)
Which should roughly translate to
Task.from("(#{select1.to_sql} UNION #{select2.to_sql} UNION #{select3.to_sql}) AS tasks")
Note the above line also fails:
Caused by PG::ProtocolViolation: ERROR: bind message supplies 0 parameters, but prepared statement "" requires 1
To summarize:
How to use Arel to build a tripple UNION query? select 1 UNION select 2 UNION select 3
Thank you
Ruby 2.5.3 Rails 5.2.4 Arel 9.0.0
You can not invoke union in an Arel::Nodes::Union, because in the method definition the ast of both objects is called. And the result of a union operation doesn't respond to ast:
def union(operation, other = nil)
if other
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
else
other = operation
node_class = Nodes::Union
end
node_class.new(self.ast, other.ast)
end
What you can do is to manually call Arel::Nodes::Union, passing as any of those arguments the result of a union:
Arel::Nodes::Union.new(Arel::Nodes::Union.new(select1, select2), select3)

Ruby on Rails - Increment by 1 from last object

I am building a user gallery application, where user can create albums and upload images to their album. I am using nested forms for images.
In my images database, I have a column called sort_order. I want to use sort_order to manage the sorting and showing user, for ex: he is seeing 3 of 12 images, where 3 is number of sort_order and 12 is number of total images in that album.
sort_order is integer and default: 0. What I looking for is to increase/increment number of sort_order for each image based on previous sort_order of last image in same album.
For ex: image 1 will have sort_order: 1, image 2 will then have previous images sort_id +1 => sort_order: 2 and so on.
Here is the code I am using now:
after_create :previous_image
def previous_image
img = user.images.order("id ASC")
img.each do |image|
image.increment!(:sort_order, self.sort_order + 1)
end
end
This does the work but in wrong sort.
id sort_order
10 3
11 2
12 1
instead of having, id: 10 sort_order: 1, its getting 3.
This is the log:
SQL (0.5ms) UPDATE "images" SET "sort_order" = ?, "updated_at" = ? WHERE "images"."id" = ? [["sort_order", 1], ["id", 398]]
user Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 160]]
Slide Load (0.1ms) SELECT "images".* FROM "images" WHERE "images"."user_id" = ? ORDER BY id DESC [["user_id", 160]]
SQL (0.1ms) UPDATE "images" SET "sort_order" = ?, "updated_at" = ? WHERE "images"."id" = ? [["sort_order", 1], ["id", 399]]
SQL (0.1ms) UPDATE "images" SET "sort_order" = ?, "updated_at" = ? WHERE "images"."id" = ? [["sort_order", 2], ["id", 398]]
user Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 160]]
Slide Load (0.1ms) SELECT "images".* FROM "images" WHERE "images"."user_id" = ? ORDER BY id DESC [["user_id", 160]]
SQL (0.1ms) UPDATE "images" SET "sort_order" = ?, "updated_at" = ? WHERE "images"."id" = ? [["sort_order", 1], ["id", 400]]
SQL (0.1ms) UPDATE "images" SET "sort_order" = ?, "updated_at" = ? WHERE "images"."id" = ? [["sort_order", 2], ["id", 399]]
SQL (0.1ms) UPDATE "images" SET "sort_order" = ?, "updated_at" = ? WHERE "images"."id" = ? [["sort_order", 3], ["id", 398]]
It starts from bottom and not top. How can I fix it?
If there is another way to do it without having the sort_order column, I am open to it.
After receiving the form data, add an index (starting from 1) to the images array.
Considering this is your images array from the form:
images = [{name: 'Image 1', data: 123}, {name: 'Image 2', data: 456}]
Use with_index(1) to attach the sort order to every element:
images.map.with_index(1).to_a
=> [[{:name=>"Image 1", :data=>123}, 1], [{:name=>"Image 2", :data=>456}, 2]]

Rails where clause is not working for some of the values

I am working on Easy Redmine PMS and I have written where query in rails Like:
Tracker.where(name: "+ Permits Required")
but it returns less values whereas when I have tried detect query for getting same data Like:
Tracker.all.detect{|i| i.name == "+ Permits Required"
And it is returning all the values. Also I noticed that when fetching the data directly in database then I get the value as Request Available whereas rails console returns Permits Required. I have tried this issue with postgresql and mysql both the databases and the issue is same. Here are the queries:
2.1.1 :001 > #tra = Tracker.find_by_name("+ Camps & Finished Food Store")
Tracker Load (32.8ms) SELECT "trackers".* FROM "trackers" WHERE "trackers"."name" = $1 LIMIT 1 [["name", "+ Camps & Finished Food Store"]]
=> nil
2.1.1 :002 > #tra = Tracker.all.detect{|i| i.name == ("+ Camps & Finished Food Store")}
Tracker Load (0.9ms) SELECT "trackers".* FROM "trackers"
AnonymousUser Load (27.4ms) SELECT "users".* FROM "users" WHERE "users"."type" IN ('AnonymousUser') ORDER BY "users"."id" ASC LIMIT 1
EasyTranslation Load (28.6ms) SELECT "easy_translations".* FROM "easy_translations" WHERE "easy_translations"."entity_id" = $1 AND "easy_translations"."entity_type" = $2 AND "easy_translations"."entity_column" = $3 AND "easy_translations"."lang" = $4 ORDER BY "easy_translations"."id" ASC LIMIT 1 [["entity_id", 3], ["entity_type", "Tracker"], ["entity_column", "name"], ["lang", "en"]]
EasyTranslation Load (1.5ms) SELECT "easy_translations".* FROM "easy_translations" WHERE "easy_translations"."entity_id" = $1 AND "easy_translations"."entity_type" = $2 AND "easy_translations"."entity_column" = $3 AND "easy_translations"."lang" = $4 ORDER BY "easy_translations"."id" ASC LIMIT 1 [["entity_id", 4], ["entity_type", "Tracker"], ["entity_column", "name"], ["lang", "en"]]
EasyTranslation Load (0.7ms) SELECT "easy_translations".* FROM "easy_translations" WHERE "easy_translations"."entity_id" = $1 AND "easy_translations"."entity_type" = $2 AND "easy_translations"."entity_column" = $3 AND "easy_translations"."lang" = $4 ORDER BY "easy_translations"."id" ASC LIMIT 1 [["entity_id", 5], ["entity_type", "Tracker"], ["entity_column", "name"], ["lang", "en"]]
2.1.1 :003 > #tra = Tracker.where(name: "+ Camps & Finished Food Store")
Tracker Load (0.8ms) SELECT "trackers".* FROM "trackers" WHERE "trackers"."name" = $1 [["name", "+ Camps & Finished Food Store"]]
Tracker.where(name: "Permits Required")

Rails: How would I retrieve records where child element includes any of another array?

I have a User and Document model, which both have tag_lists (from acts_as_taggable)
I want to find the Documents that have any of the tags a certain user has. So for example,
user.tag_list = ["ceo" , "sales"]
documentA.tag_list = ["ceo"]
documentB.tag_list = ["all-users']
documentC.tag_list = ["sales", "marketing"]
documentD.tag_list = ["marketing"]
I want to do something like
Document.where(tag_list.includes_any?("all-users" || user.tag_list))
-> which would retrieve document A,B,C.
Obviously the syntax is wrong, but you get the idea. How would I do this in an efficient way?
-------update
Just fyi, the tag_list is not a direct attribute of either User or Document model, but a active record relation from the acts_as_taggable gem: how would I call this in a .where operator?
2.1.1 :036 > Document.last.tag_list
Document Load (3.7ms) SELECT "assignable_objects".* FROM "assignable_objects" WHERE "assignable_objects"."type" IN ('Document') ORDER BY "assignable_objects"."id" DESC LIMIT 1
ActsAsTaggableOn::Tag Load (3.5ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 52], ["taggable_type", "AssignableObject"]]
=> []
This might help you:
Document.where((tag_list-["all-users", user.tag_list].flatten).size != tag_list.size)

Resources