Define a custom has_many relation - activerecord

Let's say an application has a User who can Like a Post. But also a Post can be owned by a User (on owner_id). So models would look like
class User < ApplicationRecord
has_many :likes
has_many :posts, through: :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :post
end
class Post < ApplicationRecord
has_many :likes
has_many :users, through: :likes
belongs_to :owner, class_name: 'User'
end
How can I define a relation that would return all users with any correlation to a post?
So trying to replace the users association:
class Post < ApplicationRecord
# no owner_id, lambda is called in an User::ActiveRecord_Relation context
has_many :users, -> { where('users.id = ? OR likes.post_id = ?', owner_id, id) }, through: :likes
# doesn't work because "Relation passed to #or must be structurally compatible."
def users
User.where(id: owner_id).or(users)
end
# works but isn't an association.
def users
User.joins(:likes).where('users.id = ? OR likes.post_id = ?', owner_id, id)
end
end
I'd love to be able to define something that would allow me to write
Post.includes(:users)
without
ActiveRecord::AssociationNotFoundError: Association named 'users' was not found on Post; perhaps you misspelled it?

Related

Ruby: Associations to enable querying across 4 models

I've been trying all day to figure this out and not getting anywhere. I have these 4 models. Shop Customer Credit and CreditChange
I want to return the CreditChanges for a shop's customers, NOT the customers. Like this:
latest_changes = Shop.last.customers.credit_changes
I'm trying to join like this, but its returning the customers and not the credit_changes
Shop.last.customers.joins(:credit, :credit_change).where.not(credit_changes: {date: nil})
My models look like the following:
class Shop < ActiveRecord::Base
has_many :customers, dependent: :destroy
class Customer < ActiveRecord::Base
has_one :credit, dependent: :destroy
has_many :credit_changes, through: :credit
belongs_to :shop
class Credit < ActiveRecord::Base
belongs_to :customer
has_many :credit_changes, dependent: :destroy
class CreditChange < ActiveRecord::Base
belongs_to :credit
Where did I go wrong?
You want a has-many-through relationship.
A Shop has many CreditChanges through its Customers.
Try:
class Shop
has_many :customers
has_many :credit_changes, through: :customers
end
#shop.credit_changes
You can read more in the Rails documentation
https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Try inverting the way you're looking at things:
CreditChange.joins(:credit => :customer).where(customers: {shop: Shop.last}).where.not(credit_changes: {date: nil})

Rails 5.1: polymorphic association create/update record with additional data saved in Join table

I have polymorphic association, which looks something like this:
class Permission < ApplicationRecord
belongs_to :permitable, polymorphic: true, optional: true
belongs_to :user, optional: true
enum status: {creator: 1, editor: 2}
end
class User < ApplicationRecord
has_many :permissions, class_name: 'Permission'
end
class Book < ApplicationRecord
has_many :permissions, class_name: 'Permission', as: :permitable, dependent: :destroy
has_many :users, class_name: 'User', through: :permissions
end
I need to do save/update multiple users for books and for each saved user set permission.status.
Lets say for book=Book.first I need to add:
1) user=User.find(2) with permission.creator
2) user=User.find(1) with permission.editor
In ideal world I would need to do it in one query.
How do I do this, please? Thank you!
Update
At the moment I can do:
def create
Book.create(book_params)
end
private
def book_params
params.require(:book).permit(:name, {user_ids[]})
end
This would create record in Book and add record in Permission, but would not set status there.
Probably not the best example with Permission model - it's for example purposes and maybe is confusing. Should I change that to something else?
Update 2
I've added accepts_nested_attributes_for in Book model:
class Permission < ApplicationRecord
belongs_to :permitable, polymorphic: true, optional: true
belongs_to :user
enum status: {creator: 1, editor: 2}
end
class User < ApplicationRecord
has_many :permissions, class_name: 'Permission'
end
class Book < ApplicationRecord
has_many :permissions, class_name: 'Permission', as: :permitable, dependent: :destroy
accepts_nested_attributes_for :permissions
has_many :users, class_name: 'User', through: :permissions
end
then in my controller:
def create
Book.create(:name, permissions_attributes: [{user_id: "1", status: :creator},
{user_id: "2", status: :owner}])
end
private
def book_params
params.require(:book).permit(:name, {permissions_attributes[:user_id, :status]})
end
How do I create some loop to do create dynamically, please?
If each book can only have one creator and one editor, you could just do this:
class User < ApplicationRecord
# Maybe you'd like to define e.g. `created_books` and `edited_books`?
has_many :books
end
class Book < ApplicationRecord
belongs_to :creator, class_name: 'User'
belongs_to :editor, class_name: 'User'
end
If the book can have one creator and many editors, you could instead do this:
class User < ApplicationRecord
# Maybe you'd like to define e.g. `created_books` and `edited_books`?
has_many :books
end
class Book < ApplicationRecord
belongs_to :creator, class_name: 'User'
has_many :editors, through: :book_editors
end
class BookEditor
belongs_to :editor, class_name: 'User'
belongs_to :book
end
When you create a book, pass in he creator_id and the editor_id/editor_ids (if it's a has_many relationship):
def create
Book.create(book_params)
end
private
def book_params
params.require(:book).permit(:name, :creator_id, editor_ids:[])
end
I don't see the need for polymorphism here.

Rails 4 - STI has_many :through with polymorphic association

I'm have an issue trying to combine Rails 4, STI, polymorphic associations with has_many: :through
The issue: UserConnection is not saving the correct userable_type according to the STI
class UserConnection < ActiveRecord::Base
belongs_to :userable, polymorphic: true
belongs_to :user
end
class User < ActiveRecord::Base
has_many :user_connections
has_many :tagged_posts, through: :user_connections, class_name: 'Post', source: :userable, source_type: 'Post'
end
class Publication < ActiveRecord::Base
has_many :user_connections, as: :userable, dependent: :destroy
has_many :users, through: :user_connections
end
class Post < Publication
end
Post.create(user_ids: [1, 2], body: 'yo')
UserConnection.last
=> #<UserConnection id: 1, user_id: 2, userable_id: 44, userable_type: "Publication">
userable_type should be "Post" but it's "Publication".
I tried adding the following according to all the similar StackOverflow questions:
class UserConnection < ActiveRecord::Base
belongs_to :userable, polymorphic: true
belongs_to :user
def userable_type=(class_name)
super(class_name.to_s.classify.constantize.base_class.to_s)
end
end
This didn't help.
Any ideas will greatly appreciated
turns out it's an old issue that wasn't solved yet
https://github.com/rails/rails/issues/724
https://github.com/rails/rails/issues/6786
gem patch solution:
https://github.com/appfolio/store_base_sti_class
tested it with your code and it works.

has_one :through and has_many :through

rails -v = 4.0
ruby -v = 2.1.1
I am having some serious problem with has_one :through. All of the google 1st 2 pages link are blue in color ( I have gone through all of them).
My problem is when I try to do
post = Post.last
post.build_user
It say undefined method `build_user'. My classes with associations are as follow.
class Post < ActiveRecord::Base
has_one :user_post
has_one :user, class_name: "User", through: :user_post
accepts_nested_attributes_for :user
end
class UserPost < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
class User < ActiveRecord::Base
has_many :user_posts
has_many :posts, through: :user_posts
end
It would be really great if somebody please help to out to resolve this issue.
Much obliged.
You are attempting to setup a Many-to-Many Relationship between Post and User but your current setup is incorrect.
You need to use has_many instead of has_one in Post model.
class Post < ActiveRecord::Base
has_many :user_posts
has_many :users, through: :user_posts
end
After this you can build the users as:
post = Post.last
post.users.build
UPDATE
You are getting error as undefined methodbuild_user'.because you can only usepost.build_userif association betweenPostandUserishas_one` and defined as below:
class Post < ActiveRecord::Base
has_one :user
end
class User < ActiveRecord::Base
belongs_to :post # foreign key - post_id
end
UPDATE 2
Also, logically A user has_many posts AND A post has one User so your setup should ideally be
class Post < ActiveRecord::Base
belongs_to :user # foreign key - user_id
end
class User < ActiveRecord::Base
has_many :posts
end
After this you can build the posts for a user as:
user = User.last
user.posts.build
To build a user for a post:
post = Post.last
post.build_user

Rails 3.1 - Scope for multiple objects through belongs_to association?

class Item < ActiveRecord::Base
belongs_to :account
belongs_to :category
end
class Category < ActiveRecord::Base
belongs_to :account
has_many :items
end
I'd like to do the following:
#items = #account.items.where(...)
#categories = #items.categories.order(...)
#items.categories should get all categories of #items through the belongs_to association. The best I've come up with is:
#categories = #items.map{|item| item.category }
But isn't there a scope for managing this?
Since you want the categories from an account by going through the items, I think you could do something like
has_many :categories, :through => :items
in your Account model, and then just call account.categories
Also, for the record, the map you're doing there does n+1 queries (it should be something like #categories = #items.includes(:category).map{...}

Resources