Active Record - converting a SQL query - ruby-on-rails

I'm having trouble converting a SQL query to ActiveRecord and I'm hoping you can help.
select tbl2.id
from Table1 tbl1
JOIN Table2 tbl2 ON tbl1.id = tbl2.some_column_id
where tbl1.id in (1, 2, 3, 4, 5)
and tbl2.id not in (10, 13, 22, 44, 66)
Rails Models exists and the relationship is like this:
Table2:
has_many :table1

Assumed you setup your classes with appropriate table names (table1 and table2 are not good names for rails models, btw).
Then
Table2
.select(:id).joins(:table1)
.where(table1: { id: [1, 2, 3, 4, 5] }
.where.not(id: [10, 13, 22, 44, 66])

Related

How to filter a number of records and get only the outer most records from a postgres ltree structure?

I have database records arranged in an ltree structure (Postgres ltree extension).
I want to filter these items down to the outer most ancestors of the current selection.
Test cases:
[11, 111, 1111, 2, 22, 222, 2221, 2222] => [11, 2];
[1, 11, 111, 1111, 1112, 2, 22, 222, 2221, 2222, 3, 4, 5] => [1, 2, 3, 4, 5];
[1111, 1112, 2221, 2222] => [1111, 1112, 2221, 2222];
1
|_1.1
| |_1.1.1
| |_1.1.1.1
| |_1.1.1.2
2
|_2.2
| |_2.2.2
| |_2.2.2.1
| |_2.2.2.2
3
|
4
|
5
I have implemented this in Ruby like so.
def fetch_outer_most_items(identifiers)
ordered_items = Item.where(id: identifiers).order("path DESC")
items_array = ordered_items.to_a
outer_most_item_ids = []
while(items_array.size > 0) do
item = items_array.pop
outer_most_item_ids.push(item.id)
duplicate_ids = ordered_items.where("items.path <# '#{item.path}'").pluck(:id)
if duplicate_ids.any?
items_array = items_array.select { |i| !duplicate_ids.include?(i.id) }
end
end
return ordered_items.where(id: outer_most_item_ids)
end
I have eliminated descendants as duplicates via recursion. I'm pretty sure there is an SQL way of doing this which will be the preferred solution as this one triggers n+1 queries. Ideally I would add this function as a named scope for the Item model.
Any pointers please?

Sorting Rails query results in a specific order and adding the rest to it

I have a specific order in which I want to show a number of my blog posts, but I'd also like to display regular blog posts after those selected ones.
So let's say I have posts [1, 7, 35, 36, 48] which I want to go first:
#selected_posts = Post.find([1, 7, 35, 36, 48])
But now I need to query for every other post, excluding those above:
#other_posts = Post.where.not(id: [1, 7, 35, 36, 48])
And now I need to combine those to maintain that order:
Post.find([1, 7, 35, 36, 48]) + Post.where.not(id: [1, 7, 35, 36, 48])
I'm using Postgres. Is it possible to do this in one query?
You can do order by case...
priority_ids = [1, 7, 35, 36, 48]
#all_posts = Post.all.order("CASE WHEN id IN (#{priority_ids.join(',')}) THEN 1 ELSE 2 END")
(thanks to #DeepakMahakale for correction)

How do I set up a has_many association where one model has two sets of jsonb ids in Ruby on Rails

I want to be able to query a user's games where their user_id is in the games' move's white_user_ids or black_user_ids. I also want to be able to query a game's users where its move's white_user_ids or black_user_ids correspond to users.
EDIT:
Before I start explaining my question in more detail, I came up with a workaround:
class User < ActiveRecord::Base
def games
Game.where( [ "moves -> 'black_user_ids' #> '?' OR moves -> 'white_user_ids' #> '?'", self.id, self.id ] )
end
end
class Game < ActiveRecord::Base
serialize :moves, HashSerializer
store_accessor :moves, :white_user_ids, :black_user_ids
def users
User.find((self.white_user_ids + self.black_user_ids).uniq)
end
end
Is there anything wrong with creating custom methods with the names I want, instead of writing out has_many ...? If that is a hack and bad, then please keep reading and feel free to answer.
Initial question:
Here is the most basic version of the schema for this question:
User
id
Game
id
moves - jsonb
white_user_ids - integer array
black_user_ids - integer array
I have created an expression index for both user_ids:
execute <<-SQL
CREATE INDEX moves_white_user_ids_on_games ON games USING GIN ((moves->'white_user_ids'));
CREATE INDEX moves_black_user_ids_on_games ON games USING GIN ((moves->'black_user_ids'));
SQL
I don't know how to set up the relationship between users and games. There is clearly some kind of has_many relationship, or maybe even a has_and_belongs_to_many relationship. But I am storing all the data that I would need to make the association in the games table, moves jsonb column.
Examples:
<User id: 1>
<User id: 5>
<User id: 6>
<User id: 11>
<User id: 17>
<User id: 20>
<User id: 23>
<User id: 35>
<User id: 76>
<User id: 89>
<User id: 93>
<Game id: 1, moves: {"black_user_ids"=>[88],
"white_user_ids"=>[23]}>
<Game id: 2, moves: {"black_user_ids"=>[6, 1, 11, 76, 17, 23],
"white_user_ids"=>[93, 89, 1, 35, 20, 5, 6]}>
<Game id: 3, moves: {"black_user_ids"=>[76, 68, 20, 96, 19, 3],
"white_user_ids"=>[82, 48, 29, 37, 20, 74]}>
<Game id: 4, moves: {"black_user_ids"=>[82],
"white_user_ids"=>[74, 16]}>
<Game id: 5, moves: {"black_user_ids"=>[22, 41, 25, 78, 50],
"white_user_ids"=>[24, 10, 99, 26, 1, 4]}>
User associations/scopes:
User.find(1).games would return games [2, 5], any unique game where either black_user_ids or white_user_ids has their user_id.
User.find(1).black_games would return game [2], any game where black_user_ids has their user_id.
User.find(1).white_games would return games [2, 5], any game where white_user_ids has their user_id.
Game associations/scopes:
Game.find(2).users would return users [1, 5, 6, 11, 17, 20, 23, 35, 76, 89, 93], any unique user in its black_user_ids or white_user_ids.
Game.find(3).black_users would return users [3, 19, 20, 68, 76, 96], any user in its black_user_ids.
Game.find(4).white_users would return users [16, 74], any user in its white_user_ids.
You can do this with polymorphic associations.
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
The moves table would then have id and type black or white. You can then associate that to game and query black moves or white moves.

Get unique ids from 2 has_many relationships in rails

I can't find a simple way to get the ids of pointfields from this relationship
pointtype has_and_belongs_to_many pointfields
pointfield has_and_belongs_to_many pointtypes
I do the following :
#pointtypes = current_project.points.map{|p| p.pointtype}.uniq
#pointtypes = #pointtypes - [nil] # tricky... Is this Railsy ?
#pointfield_ids = #pointtypes.map(&:pointfield_ids)
returns
[[16, 17, 18, 23, 24, 25, 26, 27, 28, 29], [16, 17, 32, 33, 34, 35, 36]]
and thus
#pointfield_ids.uniq!
is not working because there is two sub-arrays...
What I need is to get one single array with the unique ids of the pointfields (or the objects itself)

Rails 3. How to get the difference between two arrays?

Let’s say I have this array with shipments ids.
s = Shipment.find(:all, :select => "id")
[#<Shipment id: 1>, #<Shipment id: 2>, #<Shipment id: 3>, #<Shipment id: 4>, #<Shipment id: 5>]
Array of invoices with shipment id's
i = Invoice.find(:all, :select => "id, shipment_id")
[#<Invoice id: 98, shipment_id: 2>, #<Invoice id: 99, shipment_id: 3>]
Invoices belongs to Shipment.
Shipment has one Invoice.
So the invoices table has a column of shipment_id.
To create an invoice, I click on New Invoice, then there is a select menu with Shipments, so I can choose "which shipment am i creating the invoice for". So I only want to display a list of shipments that an invoice hasn't been created for.
So I need an array of Shipments that don't have an Invoice yet. In the example above, the answer would be 1, 4, 5.
a = [2, 4, 6, 8]
b = [1, 2, 3, 4]
a - b | b - a # => [6, 8, 1, 3]
First you would get a list of shipping_id's that appear in invoices:
ids = i.map{|x| x.shipment_id}
Then 'reject' them from your original array:
s.reject{|x| ids.include? x.id}
Note: remember that reject returns a new array, use reject! if you want to change the original array
Use substitute sign
irb(main):001:0> [1, 2, 3, 2, 6, 7] - [2, 1]
=> [3, 6, 7]
Ruby 2.6 is introducing Array.difference:
[1, 1, 2, 2, 3, 3, 4, 5 ].difference([1, 2, 4]) #=> [ 3, 3, 5 ]
So in the case given here:
Shipment.pluck(:id).difference(Invoice.pluck(:shipment_id))
Seems a nice elegant solution to the problem. I've been a keen follower of a - b | b - a, though it can be tricky to recall at times.
This certainly takes care of that.
Pure ruby solution is
(a + b) - (a & b)
([1,2,3,4] + [1,3]) - ([1,2,3,4] & [1,3])
=> [2,4]
Where a + b will produce a union between two arrays
And a & b return intersection
And union - intersection will return difference
The previous answer here from pgquardiario only included a one directional difference. If you want the difference from both arrays (as in they both have a unique item) then try something like the following.
def diff(x,y)
o = x
x = x.reject{|a| if y.include?(a); a end }
y = y.reject{|a| if o.include?(a); a end }
x | y
end
This should do it in one ActiveRecord query
Shipment.where(["id NOT IN (?)", Invoice.select(:shipment_id)]).select(:id)
And it outputs the SQL
SELECT "shipments"."id" FROM "shipments" WHERE (id NOT IN (SELECT "invoices"."shipment_id" FROM "invoices"))
In Rails 4+ you can do the following
Shipment.where.not(id: Invoice.select(:shipment_id).distinct).select(:id)
And it outputs the SQL
SELECT "shipments"."id" FROM "shipments" WHERE ("shipments"."id" NOT IN (SELECT DISTINCT "invoices"."shipment_id" FROM "invoices"))
And instead of select(:id) I recommend the ids method.
Shipment.where.not(id: Invoice.select(:shipment_id).distinct).ids
When dealing with arrays of Strings, it can be useful to keep the differences grouped together.
In which case, we can use Array#zip to group the elements together and then use a block to decide what to do with the grouped elements (Array).
a = ["One", "Two", "Three", "Four"]
b = ["One", "Not Two", "Three", "For" ]
mismatches = []
a.zip(b) do |array|
mismatches << array if array.first != array.last
end
mismatches
# => [
# ["Two", "Not Two"],
# ["Four", "For"]
# ]
s.select{|x| !ids.include? x.id}

Resources