Rails 4 many to many joint table - has-many-through

I'm trying to learn many to many relationship, so I have two model Order and Product, I generated with scaffold a join table orders_products with the follow migration:
create_table :orders_products do |t|
t.references :order
t.references :product
end
I have in order model:
has_many :orders_products
has_many :products, through: :orders_products
accepts_nested_attributes_for :orders_products
in product model:
has_many :orders_products
has_many :orders, through: :orders_products
accepts_nested_attributes_for :orders_products
in ordersproduct model:
belongs_to :order
belongs_to :product
in order controller:
def new
#order = Order.new
#order.save
#entry = OrdersProduct.create
#entry.product_id = Product.find_by(name: 'default_product').id
#entry.order_id = #order.id
end
def edit
#order = Order.find(params[:id])
#entries = #order.products
#order.save
end
private
def order_params
params.require(:order).permit(:name, orders_products: [:id, :order_id, :product_id])
end
end
I am getting undefined method `products when I am in edit on the line
#entries = #order.products
Someone can help me?

Use has_and_belongs_to_many :products on Order and has_and_belongs_to_many :orders on Product. Wouldn't think through is appropriate in this scenario for what you are trying to accomplish. You can also remove the has_many :orders_products lines as well.

Related

Define a custom has_many relation

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?

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.

Setting up my models with has_many through

I have become a little unstuck trying to setup my models with the correct associations, I have 3 models as follows
class Image < ActiveRecord::Base
has_many :categories
end
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
end
class ImageCategory < ActiveRecord::Base
# Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
belongs_to :image
belongs_to :category
end
The ImageCategory table is my join table as I see it which holds all my image_ids with their corresponding category_ids, as a Image can have multiple Categories
Form to create Image
permit_params :id, :title, :description, :photo,
category_ids: []
form html: { multipart: true } do |f|
inputs do
f.semantic_errors
f.input :title
f.input :description, as: :text, input_html: { rows: 10, cols: 10 }
f.input :categories, as: :check_boxes
end
end
When I try and then create an image, I get the following error:
can't write unknown attribute `image_id`
What error(s) have I made here?
I think the way that you have set up your associations is causing you this issue.
class Image < ActiveRecord::Base
has_many :image_categories
has_many :categories, through: :image_categories
end
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
end
class ImageCategory < ActiveRecord::Base
belongs_to :image
belongs_to :category
end
What your current associations describe is that a category can have many images through image_categories but image and image_categories are associated via category in your model which is not what I think you intend.
As I could see, there is a mistake in Image Model
class Image < ActiveRecord::Base
has_many :categories, through: :image_categories
has_many :image_categories
end
Hope this help you !!!
Just to add there was no need for your join model ImageCategory in this case. Check out has_and_belongs_to_many association (HABTM).
has_many:through is useful in scenarios where you would need to add additonal attributes to your ImageCategory model other than ImageId and CategoryId.
In your scenario, however, HABTM will suffice.

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