Ruby on rails 5 3 model associations - ruby-on-rails

I have 3 models, User, Product, Coupon.
A User has many Products, Product belongs to User.
A User has many Coupons, Coupon belongs to User.
My goal is to apply a Coupon to a Product. A Product can have one coupon and a Coupon can be applied to many Products. Currently I have the models set up like this:
#coupon.rb
class Coupon < ApplicationRecord
belongs_to :user
has_many :products
validates_presence_of :code, :discount_percent, :description
end
#user.rb
class User < ApplicationRecord
has_many :products
has_many :coupons
end
#product.rb
class Product < ApplicationRecord
belongs_to :user
has_one :coupon, dependent: :destroy
end
Currently a user can successfully create a coupon, but if I apply the coupon to the product and try to delete the coupon, it gives me a foreign key error.
I've thought about making the product.coupon_id = nil inside the destroy action of the coupons_controller but I feel that is a bad practice. Ex.)
#coupons_controller.rb
def destroy
products = Product.where(coupon_id: #coupon.id)
products.each do |product|
product.coupon_id = nil
product.save
end
#coupon.destroy
end
I think I have something wrong with my associations but can't seem to figure it out! Using Postgres.
I appreciate any help!

class Coupon < ApplicationRecord
belongs_to :user
has_many :products, dependent: :nullify
validates_presence_of :code, :discount_percent, :description
end

Related

Can a scope that looks up on a polymorphic be turned into an association between the two models?

Suppose an Invoice belongs_to Invoiceable, a polymorphic, being the possible invoiceable_types Subscription, SubscriptioCart and Purchase.
The Invoice table has the columns invoiceable_type and invoiceable_id. So for example, if I want to retrieve all Invoices related to a SubscriptionCart through the polymorphic, I can do Invoice.where(invoiceable_type: "SubscriptionCart").
Now how can I transform such scope into a direct association between Invoice and a SubscriptionCart through the polymorphic?
I've tried adding belongs_to :subscription_cart to the Invoice model, resulting in #invoice.subscription_cart returning nil .
This makes sense as the table invoices doesn't have a column subscription_cart_id (nor should it, as that's why we use the polymorphic).
But how do I specify what to look for in Invoiceable then?
I've tried class_name: :SubscriptionCart and foreign_key: subscription_cart_id but it still returns nil.
Stripped down models:
Invoice model:
class Invoice < ApplicationRecord
belongs_to :invoiceable, polymorphic: true
scope :subscription_cart, -> {
where(invoiceable_type: "SubscriptionCart")
}
end
SubscriptionCart model:
class SubscriptionCart < ApplicationRecord
include ::Invoiceable::Subscription
belongs_to :subscription
has_many :invoices, as: :invoiceable
end
Subscription model:
class Subscription < ApplicationRecord
include ::Invoiceable::Subscription
belongs_to :user
has_many :subscription_carts, dependent: :restrict_with_exception
has_many :invoices, as: :invoiceable, dependent: :restrict_with_exception
end
Invoiceable concern:
module Invoiceable::Subscription
extend ActiveSupport::Concern
include Invoiceable
included do
def attributes_for_invoice_items
{}.tap do |attributes|
attributes["flat_fee"] = plan_invoice_item
attributes["delivery_price"] = delivery_price_item_invoice_item if plan.deliverable?
attributes["setup_fee"] = setup_invoice_item if setup_fee_billing_pending?
attributes["per_unit"] = per_unit_invoice_item if base_plan.per_unit?
end
end
end
end

Ruby Active Record Relational

Still a newbie here, but I still couldn't get the logic right.
Currently, I have:
User has many products.
Product has 1 user with a price attribute.
I am trying to add on:
User can offer 1 price on a product sold by another user. User can offer price on multiple products.
A Product can have many offered price by multiple users.
I have currently come out with:
class User < ActiveRecord::Base
has_many :products
has_many :offered_prices
end
class Product < ActiveRecord::Base
belongs_to :user
has_many :offered_prices
end
This is what I have done so far. It still doesn't seem quite right as I am rather confused at the same time. Your help is very much appreciated! :)
Define three models:
User | OfferedPrice | Product
The association amongst them will be:
class User < ActiveRecord::Base
has_many :products
has_many :offered_prices, through: :products
end
class OfferedPrice < ActiveRecord::Base
belongs_to :user
belongs_to :product
# To make sure a user can offer price once for against a product
validates_uniqueness_of :price, scope: [:user, :product]
end
class Product < ActiveRecord::Base
has_many :offered_prices
has_many :user, through: :offered_prices
end

Rails: Querying through 2 join table associations

I have a quite complicated relation between models and are now frustrated by a SQL Query to retrieve some objects.
given a Product model connected to a category model via a has_many :through association and a joint table categorization.
Also a User model connected to this category model via a has_many :through association and a joint table *category_friendship*.
I am now facing the problem to retrieve all products, which are within the categories of the array user.category_ids. However, I can't just not manage to write the WHERE statement properly.
I tried this:
u = User.first
uc = u.category_ids
Product.where("category_id IN (?)", uc)
However this won't work, as it doesn't have a category_id in the product table directly. But how can I change this to use the joint table categorizations?
I'm giving you the model details, maybe you find it helpful for answering my question:
Product.rb
class Product < ActiveRecord::Base
belongs_to :category
def self.from_users_or_categories_followed_by(user)
cf = user.category_ids
uf = user.friend_ids
where("user_id IN (?)", uf) # Products out of friend_ids (uf) works fine, but how to extend to categories (cf) with an OR clause?
end
Category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, through: :categorizations
has_many :category_friendships
has_many :users, through: :category_friendships
Categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :product
Category_friendship.rb
class CategoryFriendship < ActiveRecord::Base
belongs_to :user
belongs_to :category
User.rb
class User < ActiveRecord::Base
has_many :category_friendships
has_many :categories, through: :category_friendships
def feed
Product.from_users_or_categories_followed_by(self) #this should aggregate the Products
end
If you need more details to answer, please feel free to ask!
Looking at the associations you have defined and simplifying things. Doing a bit refactoring in what we have to achieve.
Product.rb
class Product < ActiveRecord::Base
belongs_to :category
end
User.rb
class User < ActiveRecord::Base
has_many :categories, through: :category_friendships
scope :all_data , includes(:categories => [:products])
def get_categories
categories
end
def feed
all_products = Array.new
get_categories.collect {|category| category.get_products }.uniq
end
end
Category.rb
class Category < ActiveRecord::Base
has_many :users, through: :category_friendships
has_many :products
def get_products
products
end
end
NO NEED OF CREATING CATEGORY_FRIENDSHIP MODEL ONLY A JOIN TABLE IS NEEDED WITH NAME CATEGORIES_FRIENSHIPS WHICH WILL JUST HAVE USER_ID AND CATEGORY_ID
USAGE: UPDATED
Controller
class UserController < ApplicationController
def index
#all_user_data = User.all_data
end
end
view index.html.erb
<% for user in #all_user_data %>
<% for products in user.feed %>
<% for product in products %>
<%= product.name %>
end
end
end
I've upvoted Ankits answer but I realized there is a more elegant way of handeling this:
given:
u = User.first
uc = u.category_ids
then I can retrieve the products out of the categories by using:
products = Product.joins(:categories).where('category_id IN (?)', uc)

Rails: How to create a new entry in the join table

Let's consider a practical example: A product that has many reviews, written by clients. We have a many-to-many relationship between product and client through reviews.
class Product < ActiveRecord::Base
has_many :reviews
has_many :clients, :through => :reviews
end
class Client < ActiveRecord::Base
has_many :reviews
has_many :products, :through => :reviews
end
class Reviews < ActiveRecord::Base
belongs_to :product
belongs_to :client
end
Here, I'm using the has_many :through to create the many-to-many relation, because the review table needs to have extra attributes, like the score, content, likes,...
The user logs into my app, so I can get his data through:
client = Client.find_by_id current_user.id
He goes to the product page, so I can get product data:
product = Product.find_by_id params[:id]
How can I create client review of the product?
I tried:
review = Review.create :client => client, :product => product, :comment => params[:review][:comment]
but it gives me: MassAssignSecurity: can't mass-assign protected attributes: product, client
Any idea? Thanks in advance.
As soon as you create a Review object and explicitly pass arguments you need to make them accessible in your Review model. In this case it must be foreign keys
class Reviews < ActiveRecord::Base
belongs_to :product
belongs_to :client
attr_accessible :client_id, :product_id
end
That should work, but this's bad practice, which causes security issues. Instead of making foreign keys accessible and explicitly passing them in Review.create, I recommend to replace review.create with the following:
review = Review.new
review.client = client
review.product = product
review.comment = params[:review][:comment]
review.save
That will create a new Review object avoiding mass-assignment.
Hope this helps.
Add to ur model where the attributes are :product and :client
attr_accessible :product, :client
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html
The :client and :product attributes are private, you have to make them accesible on each class setting attr_accessible :client and attr_accessible :product respectively like:
class Reviews < ActiveRecord::Base
belongs_to :product
belongs_to :client
attr_accessible :client, :product
end
Hope this helps

rails creating model with multiple belongs_to, with attr_accessible

My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)

Resources