Delete in Many to Many Relationship Not Symmetric? - ruby-on-rails

I have two Models, Product and Category, and a join table, Categorizations, for the many-to-many relationship.
Let's say I have two objects, product and category, that are instances of the above.
products = Product.new(...)
category = Category.new(...)
product.categories << category
This successfully creates the relationship in both directions in the rails console, so that:
product.categories
category.products
are both nonempty. Next:
product.categories.delete category
will delete the value from the product object and the join table. HOWEVER it will not delete it from the category object, so that:
category.products
is nonempty, which means that the in-memory category.products object is out of sync with the actual database. It seems weird to me that creation would work symmetrically but deletion would not.
Here are the relevant models:
class Product < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations, :uniq => true
end
class Category < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :products, through: :categorizations, :uniq => true
end
class Categorization < ActiveRecord::Base
belongs_to :product, class_name: "Product"
belongs_to :category, class_name: "Category"
validates :product, presence: true
validates :category, presence: true
end
Any ideas? Thanks!

Answer: it's product.reload
This explanation is the first one I've found after hours searching:
https://stackoverflow.com/a/7449957/456280

The behavior you're observing is the way Rails is designed to behave. See the Rails Guide on associations
You might also want to look at the section on has_and_belongs_to_many (HABTM) associations. HABTM would let you get rid of your explicit Categorization model if renamed the join table categories_products.

Related

Finding entries in a join table with identical links

In my chat app I have users and chats. The tables for each of these is connected by a join table:
class User < ApplicationRecord
has_and_belongs_to_many :chats_users
has_and_belongs_to_many :chats, :through => :chats_users
end
class Chat < ApplicationRecord
has_and_belongs_to_many :chats_users
has_and_belongs_to_many :users, :through => :chats_users
end
class ChatsUsers < ApplicationRecord
belongs_to :chat, class_name: 'Chat'
belongs_to :user, class_name: 'User'
validates :ad_id, presence: true
validates :tag_id, presence: true
end
And the inverse in chat.rb.
When creating a new chat with a list of participating list of user_ids, I want to first check a chat doesn't already exist with the exact same list of associated user_ids, but I can't work out a sane way to do this. How can this be done?
has_and_belongs_to_many is only used in the case where you do not need a join model (or where you initially think you don't need it) as its headless. Instead you want to use has_many through::
class User < ApplicationRecord
has_many :chat_users
has_many :chats, through: :chat_users
end
class Chat < ApplicationRecord
has_many :chat_users
has_many :users, through: :chat_users
end
class ChatUser < ApplicationRecord
belongs_to :chat
belongs_to :user
# not needed if these are belongs_to associations in Rails 5+
validates :ad_id, presence: true
validates :tag_id, presence: true
end
You may need to create a migration to change the name of your table to chat_users and make sure it has a primary key.
has_and_belongs_to_many uses an oddball plural_plural naming scheme that will cause rails to infer that the class is named Chats::User since plural words are treated as modules. While you can work around that by explicitly listing the class name its better to just align your schema with the conventions.
If your still just messing about in development roll back and delete the migration that created the join table and run rails g model ChatUser chat:belongs_to user:belongs_to to generate the correct table with a primary key and timestamps.
If you want to select chats connected to a given set of users:
users = [1,2,3]
Chat.joins(:users)
.where(users: { id: users })
.group(:id)
.having(User.arel_table[Arel.star].count.eq(users.length))
.exists?
Note that you don't really need to tell ActiveRecord which table its going through. Thats the beauty of indirect associations.

ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation How can i get this relationshop objects?

As i mentioned on the question..
I know this error and i know how associations work
In my case i have ProductRepository model which belongs_to :category
So of course when i'm trying to delete this category which this ProductRepository object belongs to i should get this error and it's totally fine and i know how to handle this exception
Now let's say that i have a many models which referring to categories like
Product, ProductRepositoy, Post, etc...
My question here, how can i get a list of this objects which belongs to categories, or even get a class name array or whatever
So when i handle this exception i can send a message to ask the user to check and delete the following before deleting this category ?
Example:
Once user delete category... if it has any items which belongs to it
he should get a message saying Please check the following (Posts, Products, etc..)
For who is going to answer, all i need is how to get this objects or even class names only
And i can do the rest
Thanks in advance :)
Here is the file, after removing what isn't necessary for the question
category.rb
class Category < ActiveRecord::Base
has_many :brands, :dependent => :destroy
has_many :products, :dependent => :destroy
end
product_respository.rb
class ProductRepository < ActiveRecord::Base
belongs_to :category
has_many :unity_folders, as: :product, dependent: :destroy
validates :name, :price, :barcode, presence: true
validates :barcode, uniqueness: true
end
product.rb
class Product < ActiveRecord::Base
belongs_to :category
belongs_to :brand
belongs_to :organization
has_many :unity_folders, as: :product, dependent: :destroy
has_one :product3d
belongs_to :shop
end

Database creation/saving with a many-to-many relationship

I have two tables, item and category, and a joined table named Items_Categories. The one restriction I have is that an item HAS to have a category.
In my Item Model, I have this code:
class Item < ActiveRecord::Base
has_many :items_categories
has_many :categories, through: :items_categories
has_many :item_orders
has_many :orders, through: :item_orders
even though categories is not a column in my Items database.
Here's my ItemsCategory model:
class ItemsCategory < ActiveRecord::Base
belongs_to :item
belongs_to :category
end
Here's my Category Model:
class Category < ActiveRecord::Base
has_many :items_categories
has_many :items, through: :items_categories
end
In my test, I have this issue:
test 'it is valid' do
category = Category.create(name: "hot beverages")
item = category.items.create(title: "fjdkasf", description: "fjakdsf", price: 20901290)
assert item.valid?
end
Basically, this test fails because I cannot create an item until it item.categories is not nill. But the validation doesn't pass until I create the item in item_categories. So... what do I do? Basically I can't pass the validation when I create the item because the item doesn't yet have a category. But I want to make sure that every time I create an item, it has an associated item. So what do I do?
Validation with has_many :through relations can be tricky sometimes, there's a hint in the has_many :through documentation that you should define the inverse_of attribute on your join model:
If you are going to modify the association (rather than just read from
it), then it is a good idea to set the :inverse_of option on the
source association on the join model. This allows associated records
to be built which will automatically create the appropriate join model
records when they are saved. (See the 'Association Join Models'
section above.)
class ItemsCategory < ActiveRecord::Base
belongs_to :item, inverse_of: :items_categories
belongs_to :category, inverse_of: :items_categories
end
To be honest, I'm not 100% sure this will fix it, but give it a try and report back.

Create new parent record with has_many :through relationship for existing children

I am working on a Ruby on Rails API (version 4.0) to create and update invoices. The relationship between invoices and products is a has_many trough: relationship. Imagine I have product 1, 2, & 3. I am having trouble creating a new invoice that contains product 1 & 3.. When I run the code below I get the error:
Unknown primary key for table invoices_products in model InvoicesProduct.
This error doesn't really make sense to me since InvoicesProduct is a join table and shouldn't require a primary key.
One tricky part about the design is that it needs to track which employee added which products to the invoice, which is why invoices_product has employee_id. It does not seem to be the cause of the problem at the moment. Here is the DB design of the tables in questions:
InvoicesController
This is the code I currently have in the controller. The error message occurs on the first line of create:
def create
invoice = Invoice.new(create_invoice_params)
invoice.created_by = #current_user
# eventually need to set employee_id on each invoice_product,
# but just need to get it working first
# invoice.invoices_products.map!{|link| link.employee = #current_user }
invoice.save
respond_with invoice
end
def create_invoice_params
params.fetch(:invoice).permit(:customer_id, :status_code, :payment_method_code, product_ids: [])
end
Invoice
# /app/models/invoice.rb
class Invoice < ActiveRecord::Base
validates_presence_of :customer
validates_presence_of :created_by
belongs_to :customer, inverse_of: :invoices
belongs_to :created_by, inverse_of: :invoices, class_name: 'Employee'
has_many :invoices_products, inverse_of: :invoice
has_many :products, through: :invoices_products
end
InvoicesProduct
class InvoicesProduct < ActiveRecord::Base
validates_presence_of :invoice
validates_presence_of :product
validates_presence_of :employee
belongs_to :product, inverse_of: :invoices_products
belongs_to :invoice, inverse_of: :invoices_products
belongs_to :employee, inverse_of: :invoices_products
end
Product
# /app/models/product.rb
class Product < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 100 }
has_many :invoices_products, inverse_of: :product
has_many :invoices, through: :invoices_products
end
Request
This is what I've got in mind for a working request, the solution doesn't need to match this, but its what I've been trying
{
"invoice": {
"customer_id": "1",
"product_ids": ["1", "5", "8"]
}
}
I was able to fix the relationship by adding a primary key to the invoices_products. For some reason I was under the impression that join tables did not require a primary key for has_many :through relationships. However, looking at the example on the Rails guide site, the example join table does have a primary key.
That is because you are using has_many :through. If you don't want id (or any other additional field) in the join table, use has_many_and_belongs_to instead

How make categories relationships with several model?

I have next models: Articles, Announcements, Catalogs and Media.
For item of every model I need to create a subcategory and a category. I will plan to create a relationship table with two columns: parend_id and child_id, and a column for every model with category_id.
How many relationship models I should create?
One for all?
Or one relationship model for every model?
No need to create a relationship model instead just use belongs_to relationship
because you said following i guess belongs_to relations should do it
For item of every model I need create subcategory and category.
So just add subcategory_id and category_id in your tables: Articles, Announcements, Catalogs and Media and establish has_many belongs_to relationship
EDIT But if you insist for relationship model then I would suggest to use one relationship model for every model.
Last EDIT: I really think you should use belongs_to, has_many relation
Class Article < ActiveRecord::Base
belongs_to :category
belongs_to :subcategory
end
class Category < ActiveRecord::Base
has_many :articles
# the same relation(has_many) will be for Announcements, Catalogs and Media
has_many :subcategories
end
class Subcategory < ActiveRecord::Base
has_many :articles
belongs_to :category
end
In this way you can get all articles using category.subcategories.articles
If the category has only one Subcategory then change the relation between them to has_one and your syntax will become category.subcategory.articles
I would personally use a has_many :through relationship with a polymorphic association in a single join table:
#app/models/article.rb
Class Article < ActiveRecord::Base
has_many :categorizable_categories
has_many :categories, through: :categorizable_categories
end
class Article < ActiveRecord::Base
has_many :tags, as: :taggable,dependent: :destroy
has_many :categories, through: :tags
end
class Category < ActiveRecord::Base
has_many :tags, dependent: :destroy
has_many :articles, through: :tags, source: :taggable, source_type: 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, polymorphic: true
belongs_to :category
end
This will allow you to call the categories for each model with the likes of #article.categories
To achieve the parent & child categories, I'd recommend using something like the Ancestry gem - you'd set up an ancestry column in your join table - basically allowing you to create relations between a model's categories directly

Resources