I have a many to many association between two models:
class User < ActiveRecord::Base
has_many :user_works
has_many :works, through: :user_works
end
class UserWork < ActiveRecord::Base
belongs_to :user
belongs_to :work
end
class Work < ActiveRecord::Base
has_many :user_works
has_many :users, through: :user_works
end
I have a filter by works, containing several works (ids).
My task is to filter users by works and order them by count of matches.
Thanks in advance.
I guess you need to group by works, and order by count
That's how to sort from less works to more:
User.joins(:works).group("user_works.user_id").order("COUNT(*)")
That's how to sort from more works to less:
User.joins(:works).group("user_works.user_id").order("COUNT(*) DESC")
Upd.
If you want to have some extra filtering just add where clause
User.joins(:works).where("user_works.work_id in #{filter_string}").group("user_works.user_id").order("COUNT(*) DESC")
Related
I have two models with a relation like this:
class Ticket < ActiveRecord::Base
belongs_to :group
end
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :tickets, as: :assignable
end
class Group < ActiveRecord::Base
has_many :tickets, -> { order(:created_at) }
has_and_belongs_to_many :users
end
I need to get all tickets belonging to the same groups the user has.
How can I accomplish that? Thank you so much!
As things stand, your relations are incomplete and so Rails won't work properly. If User has_many Tickets then Tickets must belong_to (or at least has_one) User. Alternatively, User can have_many Tickets through Group, which seems more likely in this case.
However, even then, it's not clear what your Group model is doing. Particularly, it's not clear how you intend it to relate to User - this looks like quite a complex relationship.
To start with, though, try and set the models up like this:
class Ticket < ApplicationRecord
belongs_to :group
end
class Group < ApplicationRecord
belongs_to :user
has_many :tickets, dependent: :destroy
end
class User < ApplicationRecord
has_many :groups, dependent: :destroy
has_many :tickets, through: :groups
end
(You'll see that I've also inherited these models from ApplicationRecord, which is how I've always done it.)
If you set it up as above, you can get your ticket records with a simple #user.tickets.
If this works, you can then add the extra HABTM relationship for Groups and Users. But be aware that HABTM relationships can be complex and there are good and bad ways to use them.
(If the primary relationship you really want is Groups > Users > Tickets then let me know and I can adjust accordingly.)
So currently i have in a app:
class Post < ApplicationRecord
belongs_to :category
end
and
class Category < ApplicationRecord
has_many :posts
end
which works fine as expected. However, i need to add multiple categories to a post . I thought about using has_many_and_belongs_to for each of them to acquire this but some trouble implementing this. It seems like a need to add an join table? If so, how would one look like with the setup shown above?
Any ideas?
I appreciate any input! Thanks in advance!
The table should be named categories_posts (categories comes first because of alphabetical sequence) and contain post_id and category_id integer columns (indexed, most probably). The it's as simple as:
class Post < ApplicationRecord
has_and_belongs_to_many :categories
end
class Category < ApplicationRecord
has_and_belongs_to_many :posts
end
You can create join table adding migration using:
rails g migration CreateJoinTableCategoryPost category post
Alternatively you can use has_many :through to have more control over a join table.
Advantages of using :through for many to many relationships
With has_many :through relationship you can have a model which will allow you to add validation, callbacks.
If you initially take some extra efforts to setup many to many relationship using through it can save a lot of time and headache in future
What if in future you want save the more information on join table like some custom sort, information about how the tables are associated which will not be allowed with has_and_belongs_to_many
Example
class Post < ApplicationRecord
has_many :categories, through: :post_categories
has_many :post_categories
end
class Category < ApplicationRecord
has_many :posts, through: :post_categories
has_many :post_categories
end
Adding relationship model with rails generator command
rails g model post_category category_id:integer post_id:integer custom:text
class PostCategory < ApplicationRecord
belongs_to :category
belongs_to :post
end
I'm having trouble figuring out the proper way of retrieving all children of multiple parents through association chaining.
To simplify I have three models:
class Customer < ActiveRecord::Base
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
After creating a few objects I tired to use the example from rails guides (association basics: 4.3.3.4 includes):
Customer.first.invoices.line_items
It returns:
undefined method `line_items' for #<Customer::ActiveRecord_Associations_CollectionProxy
Is grandparent.parents.children not usable?
EDIT
I'm not searching for the grandparent.parents.first.children, but all children of all parents in the collection, rails guides state:
If you frequently retrieve line items directly from customers (#customer.orders.line_items),
As a valid operation, I would like to know if that is a mistake.
FINAL As stated in the comments of the selected answer: in ActiveRecord: scopes are chainable but associations are not.
The customer.invoices.line_items cannot work the way you want to, since the has_many always is linked to a single record. but you can achieve what you want (if I understand correctly) using has_many through
as follows:
class Customer < ActiveRecord::Base
has_many :invoices
has_many :line_items, through: :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
and now you can write:
customer.line_items
and it will return all line_items which are connected to a customer's invoices.
Customer.first.invoices.first.line_items
Or if you want all of the data together, you can do something like:
results = Customer.first.invoices.includes(:line_items)
Then you may access data with no DB call, by looping results. For first data ex: results.first.line_items
Hope it helps!
Customer.first.invoices will return an collection (like an array) of invoices. The line_items method isn't defined for a collection, but its defined for an invoice. Try Customer.first.invoices.first.line_items
EDIT - If you always want the orders to include the line items, you can just do:
class Customer < ActiveRecord::Base
has_many :orders, -> { includes :line_items }
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end
I have two models: Orders and Items. This is a many to many relationship, but I'm unsure if HMT is correct
Order model:
-user_id
-transaction_cost
-delivery_time
Item model:
-price
-name
Orders should be able to get all items in the order
Items do not need to be able to get all orders
The convention on this is to use the names of both models. A good name might be ItemOrders. Has many through is almost certainly a correct choice here.
class Order < ActiveRecord::Base
has_many :item_orders, dependent: :destroy
has_many :items, through: :item_orders
end
class Item < ActiveRecord::Base
has_many :item_orders, dependent: :destroy
has_many :orders, through: :item_orders
end
class ItemOrder < ActiveRecord::Base
belongs_to :item
belongs_to :order
end
Now you just have another ActiveRecord model, and you can add to it as you'd like. It will also be helpful for debugging. You can even use a model/scaffold generator to generate these:
rails g model item_order order:references item:references
This way you get the migrations correct right away. Nothing needs to be altered on your other models except for the above code.
Let's say I have an app where users could rate books. Tables are users(id), books(id) and rating(user_id, book_id, value). I've made these models
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class User < ActiveRecord::Base
has_many :ratings
end
class Book < ActiveRecord::Base
has_many :ratings
end
I want to get a list of all (both rated and unrated) books with their ratings made by current user. It's easy in SQL with outer join but I can't figure out a way to do it in Rails 3.
According to LEFT OUTER joins in Rails 3 you'll have to specify the outer join in SQL...
it's quite simple in rails too. You probably should add a relationship in user with books as well.
class User < ActiveRecord::Base
has_many :ratings
has_many :users, :through => :ratings
end
current_user.books.includes(:ratings).all
should work.