Rails model belongs to either one model or another - ruby-on-rails

For a CRM app, I want to be able to associate a Person model directly to an Account model or to a Company model which in turn is associated to an Account model. Also, I want to associate an Address model to either a Company or a Person. This is what I have in mind:
class Account
has_many :Persons
has_many :Companies
end
class Person
belongs_to :Account
belongs_to :Company
has_one :Address
end
class Company
belongs_to :Account
has_many :Persons
has_one :Address
end
class Address
belongs_to :Person
belongs_to :Company
end
So an Account would be either a "person account" or a "business account" depending on the association. They would be mutually exclusive. I plan to have the foreign keys account_id and company_id in the Person table. By the same token I would have the foreign keys person_id and company_id in the Address table. One foreign key would be null in each case.
Is this okay in Rails? If not, any recommendations would be greatly appreciated.

Take a look at polymorphic associations. I think that's what you are looking for:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
class Account
belongs_to :User, :polymorphic => true
end
class Person
belongs_to :Account, :as => :User
belongs_to :Company
has_one :Address, :as => :User
end
class Company
belongs_to :Account, :as => :User
belongs_to :Persons
has_one :Address, :as => :User
end
class Address
belongs_to :User, :polymorphic => true
end
...
Greetings Sven

Related

How to handle foreign key in FactoryGirl

I have a user model and a follower model, such that a user can have many followers. So in schema of follower model I have user_id column and a follower_by_user_id column. So in follower model a user can be followed by many followers. User id's are stored in user_id column and followers id's are whose id's are stored in as followed_by_user_id.
class User < ActiveRecord::Base
has_many :followed_users, :class_name => 'Follower', :foreign_key => 'user_id'
has_many :followers, :class_name => 'Follower', :foreign_key => 'followed_by_user_id'
validates :email, presence: true, format:{ with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i}
validates :name,presence:true
end
Above is user model
class Follower < ActiveRecord::Base
belongs_to :user
belongs_to :followed_by_user, :class_name => 'User', :foreign_key => 'followed_by_user_id'
validates :user, :followed_by_user, presence:true
validates_associated :user, :followed_by_user
end
above is follower model
FactoryGirl.define do
factory :user do
name {Faker::Name.name}
email {Faker::Internet.email}
end
factory :follower do
user
followed_by_user_id
end
followed_by_user_id is basically a user id only, or we can say user_id is foreign key for followed_by_user_id column. Im plain English followed_by_user_id is an id of an user who is following to some other user. So If any body can help how to include this foreign key relationship in follower factory for follower_by_user_id column?
Thanks in advance.
You can use association in your factory, like this (more info in the docs):
association :followed_by_user, factory: :user
You don't need so much complexity, use just:
followed_by_user factory: :user
This works like a charm.

Association rules in rails

I have a User model, a Listing model and an Order model. A user can either place an order or publish a listing which others can place an order for. Thus, a User can be customer as well as supplier.
My Order model has listing_id, from_id and to_id.
My question is, how can I set up associations between these models ? I read the rails guide on associations but the example there were dealing with separate customer and supplier models.
class User < ActiveRecord::Base
has_many :listings, :foreign_key => :supplier_id, :inverse_of => :supplier
has_many :orders, :foreign_key => :customer_id, :inverse_of => :customer
end
class Listing < ActiveRecord::Base
belongs_to :supplier, :class_name => 'User'
belongs_to :order
end
class Order < ActiveRecord::Base
belongs_to :customer, :class_name => 'User'
has_many :listings
end

Product orders between 2 users

I have three models: User, Product, Offer and a problem with the relationship between these models.
Scenario:
User 1 posts a product
User 2 can send User 1 an offer with an price e.g $ 10
User 1 can accept or reject the offer
My questions are now:
What is the right relationship between User, Product and Offer?
How can I handle those "accept or reject" actions?
Is there maybe a better solution?
User model:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :avatar, :screen_name
has_many :products
has_many :offers,:through => :products
end
Product model:
class Product < ActiveRecord::Base
attr_accessible :content, :price, :title, :tag_list, :productimage, :user_id
belongs_to :user
has_many :offers, :through => :users
end
Offer model:
class Offer < ActiveRecord::Base
attr_accessible :offer_price, :status, :user_id, :product_id
has_many :products
has_many :users, through: :products
end
Thanks in advance :)
EDIT:
I am using Rails 3.2.8
Warning: here comes a small novel.
Part 1: setting up the associations
I'd recommend reading the Rails guide on associations thoroughly, bookmark it, and read it again, because this is a key thing to understand properly, and can be a bit tricky - there are lots of options once you go beyond basic associations.
One thing to notice about your app is that your users have two roles, buyers and sellers. You're going to need to be careful with the names of your associations - Does #user.offers return the offers the user has made, or the offers the user has received? You might want to be able to put lists of both these things in the user's profile.
The basic relationships you're describing are fairly simple:
A user can sell many products, so User has_many :products and Product belongs_to :user
A user can make many offers, so User has_many :offers and Offer belongs_to :user
A product may receive many offers so Product has_many :offers and Offer belongs_to :product
That's all well and good, and you could certainly get by just doing this - in which case you can skip down to Part 2 :)
However, as soon as you start trying to add the through relationships the waters are going to get muddy. After all,
Offer belongs_to :user (the buyer), but it also has a user through product (the seller)
User has_many :products (that they are selling), but they also have many products through offers (that they are buying - well, trying to buy).
Aargh, confusing!
This is the point when you need the :class_name option, which lets you name an association differently to the class it refers to, and the :source option, which lets you name associations on the 'from' model differently to the 'through' model.
So you might then form your associations like this:
# User
has_many :products_selling, class_name: 'Product'
has_many :offers_received, class_name: 'Offer',
through: :products_selling, source: :offers
has_many :offers_made, class_name: 'Offer'
has_many :products_buying, class_name: 'Product',
through: :offers_made, source: :product
# Product
belongs_to :seller, class_name: 'User', foreign_key: :user_id
has_many :offers
has_many :buyers, class_name: 'User', through: :offers
# Offer
belongs_to :product
belongs_to :buyer, class_name: 'User', foreign_key: :user_id
has_one :seller, class_name: 'User', through: :product
Although if you renamed your user_id columns to seller_id in the products table, and buyer_id in the offers table, you wouldn't need those :foreign_key options.
Part 2: accepting/rejecting offers
There's a number of ways to tackle this. I would put a boolean field accepted on Offer and then you could have something like
# Offer
def accept
self.accepted = true
save
end
def reject
self.accepted = false
save
end
and you could find the outstanding offers (where accepted is null)
scope :outstanding, where(accepted: nil)
To get the accept/reject logic happening in the controller, you might consider adding new RESTful actions (the linked guide is another one worth reading thoroughly!). You should find a line like
resources :offers
in config/routes.rb, which provides the standard actions index, show, edit, etc. You can change it to
resources :offers do
member do
post :accept
post :reject
end
end
and put something like this in your OffersController
def accept
offer = current_user.offers_received.find(params[:id])
offer.accept
end
# similarly for reject
Then you can issue a POST request to offers/3/accept and it will cause the offer with id 3 to be accepted. Something like this in a view should do it:
link_to "Accept this offer", accept_offer_path(#offer), method: :post
Note that I didn't just write Offer.find(params[:id]) because then a crafty user could accept offers on the behalf of the seller. See Rails Best Practices.
Your models are good enough, except for the relations. The confusion starts when you are trying to differentiate the owned products vs interested products(offered) and product owner vs interested users(users who placed the offer). If you can come up with a better naming convention, you can easily fix it.
1. Better relations
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :avatar, :screen_name
has_many :owned_products, :class_name => "Product"
has_many :offers
has_many :interested_products, :through => :offers
end
class Offer < ActiveRecord::Base
attr_accessible :offer_price, :status, :user_id, :product_id
belongs_to :interested_user, :class_name => "User", :foreign_key => :user_id
belongs_to :interested_product, :class_name => "Product", :foreign_key => :product_id
end
class Product < ActiveRecord::Base
attr_accessible :content, :price, :title, :tag_list, :productimage, :user_id
belongs_to :owner, :foreign_key => :user_id, :class_name => "User"
has_many :offers
has_many :interested_users, :through => :offers
end
With these relations I think you can get all the basic information you would be interested.
For Example,
#product = Product.find(1)
#product.owner # would give you the user who created the product
#product.interested_users # would give you users who placed an offer for this product
#user = User.find(1)
#user.owned_products # would give you the products created by this user
#user.interested_products # would give you the products where the user placed an offer
2. Handling accept and reject actions.
From your description, I see there can be 2 possible state changes to an offer, "created" -> "accept" or "created" -> "reject". I suggest you to look at state_machine. State machine will add nice flavor to your model with its helper methods, which I think will be very useful in your case. So your Offer model will look something like this,
class Offer < ActiveRecord::Base
# attr_accessible :title, :body
attr_accessible :offer_price, :status, :user_id, :product_id
belongs_to :interested_user, :class_name => "User", :foreign_key => :user_id
belongs_to :interested_product, :class_name => "Product", :foreign_key => :product_id
state_machine :status, :initial => :created do
event :accept do
transition :created => :accepted
end
event :reject do
transition :created => :reject
end
end
end
#cool helper methods
#offer = Offer.new
#offer.accepted? #returns false
#offer.reject #rejects the offer
#offer.rejected? #returns true
I hope this gives you a better picture.
How about
class User < ActiveRecord::Base
has_many :products # All products posted by this user
has_many :offers # All offers created by this user
end
class Product < ActiveRecord::Base
belongs_to :user # This is the user who posts the product (User 1)
has_many :offers
end
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :user # This is the user who creates the offer (User 2)
# Use a 'state' field with values 'nil', 'accepted', 'rejected'
end
For your scenario:
# User 1 posts a product
product = user1.products.create
# User 2 can send User 1 an offer with an price e.g $ 10
offer = user2.offers.create(:product => product)
# User 1 can accept or reject the offer
offer.state = 'rejected'
You could refine this depending on your needs - e.g. if the same product could be posted by different users.

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 3 / Active Record - Association problems with additional model in assignment table

I currently have a setup that links the models User, Dealer and Role together. User and Dealer is many to many, and is working as expected with a Dealer_user assignment table.
The problem is that I want to have roles assigned to the user that are specific to the dealer also (i.e. a user could be a Sales Manager and a Parts Manager in one dealership, while being a Sales Manager and a Director in another).
In order to do this, I have a Role model (which belongs to a Role_type). Role should belong to Dealer_user, and Dealer_user has many Roles.
The intention is that I will be able to do dealer.users.where(:id => user.id).first.roles and it will return only the roles specific to that dealership.
The problem I have is that when I run the following test code: dealer.users.where(:id => user.id).first.roles.create(:role_type_id => 1 + Random.rand(4))
I get an error: Cannot modify association 'User#roles' because the source reflection class 'Role' is associated to 'DealerUser' via :has_many.
Can anyone suggest what I am doing wrong with my models (which are below)?
NOTE: The belongs_to relationship that Role has with Dealer_user is polymorphic because it could also belong to Sale_user or other association tables, which require the same functionality as Dealer.
class Dealer < ActiveRecord::Base
attr_accessible :name, :address_id
has_many :dealer_users
has_many :users, :through => :dealer_users
has_many :roles, :through => :dealer_users
end
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :dealer_users
has_many :dealers, :through => :dealer_users
has_many :roles, :through => :dealer_users
end
class DealerUser < ActiveRecord::Base
attr_accessible :dealer_id, :user_id
belongs_to :dealer
belongs_to :user
has_many :roles, :as => :role_originator
end
class Role < ActiveRecord::Base
attr_accessible :role_type_id
belongs_to :role_type
belongs_to :role_originator, :polymorphic => true
end
Edit: No luck so far - can anyone help?
I would use has_many through association with the roles table. Get rid of the dealer_user table, and add columns to the roles table dealer_id and user_id
Then your models would look something like this:
class Dealer < ActiveRecord::Base
has_many :users, :through => :roles
has_many :roles
end
class User < ActiveRecord::Base
has_many :dealers, :through => :roles
has_many :roles
end
class Role < ActiveRecord::Base
belongs_to :dealer
belongs_to :user
end
That should make it easier to do the types of queries you're trying. The rails guide has a really good overview here

Resources