Rails query for instances outside association condition - ruby

Product model association:
class Product < ActiveRecord::Base
has_many :likes
has_many :liking_users, through: :likes, source: :user
end
I want to find all Products which the current user has not liked.
Basically, all products except Product.joins(:liking_users).where(users: {id: current_user.id})
Can you think of an efficient query for this?

Product.joins(:liking_users).where.not(users: {id: current_user.id})

Related

How to join 1:N tables in rails and instead of multiple rows for record get one row with extra column containing array of IDs from joined table

I have 3 models
class Mission < ActiveRecord::Base
belongs_to :guild
end
class Guild < ActiveRecord::Base
has_many :missions
has_many :guild_coordinators, :dependent => :destroy
has_many :coordinators, :through=> :guild_coordinators, :class_name => "Associate"
end
class GuildCoordinator < ActiveRecord::Base
belongs_to :guild
belongs_to :coordinator, :class_name => "Associate"
end
If I do
Mission.joins(:guild => :guild_coordinators)
I get row for every guild -> guild coordinator association
Is it possible to get unique records for Missions with joined Guilds and in one column get IDs of all coordinators in an Array?
edit:
expected result is something like this:
#<ActiveRecord::Relation [#<Mission id: 13, fy: 2018, guild_id: 31, name: "test mission", status: 0, coordinators: [1,2,3,5,8]>
my database is postgres
as output I need Active Record relation for gem ajax-datatables-rails
With Postgresql you can use array_agg aggregate function:
Mission.
joins(guild: :guild_coordinators).
select('missions.*, array_agg(guild_coordinators.id) as coordinators').
group(:id)
And you get exactly ActiveRecord::Relation, which will contain(after call) Mission objects with additional field coordinators:Array.
The second option is to use .includes like my or #garrett-motzner comments show.

Query an ActiveRecord on several tables?

Sorry to ask this question but I'm really newbie with Ruby and I need help to update several records on my database.
I'm using ActiveRecord to query the database. Let say I have a table Product that contains SubProduct that also contains SubSubProduct. Now I would like to write a simple query to get all SubSubProduct of Product.
To get a list of SubSubProduct I usually do this
ssp = SubSubProduct.where(sub_sub_type: "example")
Now to use a where clause on relational element how can I do
ssp = SubSubProduct.where(sub_sub_type: "example", SubProduct.Product.type: "sample")
Set up ActiveRecord associations in your models:
#app/models/product.rb:
class Product < ActiveRecord::Base
has_many :sub_products
end
#app/models/sub_product.rb:
class SubProduct < ActiveRecord::Base
belongs_to :product
end
#app/models/sub_sub_product.rb:
class SubSubProduct < ActiveRecord::Base
belongs_to :sub_product
end
What you wanted:
ssp = SubSubProduct.where(sub_sub_type: "example", SubProduct.Product.my_type: "sample")
The correct syntax:
ssp = SubSubProduct.includes(sub_product: :product).where(sub_sub_type:"example", products: {my_type: "toto"})
This is performed via associations.
class SubSubProduct
has_many :products
end
Then you can do things like
sub_product.products
and it will product all the products associated with them.
Try a nested includes:
`Product.includes(subproducts: :subsubproducts)'
For this, you'll want to set up ActiveRecord associations in your models:
In product.rb:
class Product < ActiveRecord::Base
has_many :sub_products
has_many :sub_sub_products, through: :sub_products
end
In sub_product.rb:
class SubProduct < ActiveRecord::Base
belongs_to :product
has_many :sub_sub_products
end
In sub_sub_product.rb:
class SubSubProduct < ActiveRecord::Base
belongs_to :sub_product
end
Then, if you have a Product and want its SubSubProducts, you can use:
# p is a Product object
p.sub_sub_products

ActiveRecord - Finding all objects with shared attributes in a join model

I have three models
class Boat < ActiveRecord::Base
belongs_to :captain
has_many :boat_classifications
has_many :classifications, through: :boat_classifications
end
class Classification < ActiveRecord::Base
has_many :boat_classifications
has_many :boats, through: :boat_classifications
end
class BoatClassification < ActiveRecord::Base
belongs_to :boat
belongs_to :classification
end
I'm trying to write a simple ActiveRecord query to find all the boats of type sailboat. Something like Boat.where(classifications: "Sailboat")
I think this could work:
Boat.joins(:classifications).where(classifications: { name: 'Sailboat' }) # name or whatever field contains Sailboat
Generates this query:
SELECT `boats`.* FROM `boats` INNER JOIN `boat_classifications` ON `boat_classifications`.`boat_id` = `boats`.`id` INNER JOIN `classifications` ON `classifications`.`id` = `boat_classifications`.`classification_id` WHERE `classification`.`name` = 'Sailboat'
I think you want something like this:
Boat.includes(:classifications).where(classifications: {id: Classification.sailboats})
For this to work, you also need a scope on Classification like this:
def self.sailboats
where(name: "Sailboat")
end

How can I do a LIKE query using an association?

Using ActiveRecord, how can I do a LIKE query based on a property of an association? Specifically, I'm looking for something that works with polymorphic associations.
class Invoice < ActiveRecord::Base
has_one :private_note, class_name: '::Note', as: :noteable,
conditions: {label: 'private'}
has_one :public_note, class_name: '::Note', as: :noteable,
conditions: {label: 'public'}
end
class Note < ActiveRecord::Base
belongs_to :noteable, polymorphic: true
validates :content, :presence: true
end
I want to find invoices whose private_note has a content column containing the word "friendly".
Use #merge
This can be accomplished with the .merge method.
Invoice.joins(:private_note).merge(Note.where("content LIKE ?", '%friendly%'))

Ruby on Rails: Associations when a user likes a song

I'm trying to figure out the best way to setup my database and models for the following scenario.
A user can like an infinite number of songs.
A song can be liked once by an infinite number of users.
I have these tables:
songs, users, likes etc... Following RoR conventions.
The table named likes has these foreign keys: user_id, song_id. And also a field named 'time' to save a timestamp when the song was liked.
I'm not sure of how to do this, I would like to be able to use code like this in my controllers:
User.find(1).likes.all
This should not return from the likes table, but join the songs table and output all the songs that the user likes.
What are the best practises to achieve this in Ruby on Rails following their conventions?
Unless you need to act specifically on the likes table data, the model itself is probably not necessary. The relationship is easy:
class User < ActiveRecord::Base
has_and_belongs_to_many :songs
end
class Song < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will join through the currently non-existent song_users table. But since you want it to join through likes you can change each one to this:
has_and_belongs_to_many :songs, :join_table => 'likes'
If you want to be able to call User.find(1).likes and get songs, then change the user's version to this:
class User < ActiveRecord::Base
has_and_belongs_to_many :likes, :join_table => 'likes', :class_name => 'Song'
end
And you could change the songs version to something like this:
class Song < ActiveRecord::Base
has_and_belongs_to_many :liked_by, :join_table => 'likes', :class_name => 'User'
end
This will give you Song.find(1).liked_by.all and give you the users (You could keep it to users if you wanted using the first version)
More details on habtm relationships can be found here: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Edit to add
If you want to act on the join table for whatever reason (you find yourself needing methods specifically on the join), you can include the model by doing it this way:
class User < ActiveRecord::Base
has_many :songs, :through => :likes
has_many :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :users, :through => :likes
has_many :likes
end
This will let you do User.find(1).songs.all, but User.find(1).likes.all will give you the join data

Resources