Rails: Linking two models that inherit the same model - ruby-on-rails

This is a little bit tricky, so if you need more information, don't hesitate!
I have two models, Store and Consumer that are linked by two ways:
1/ Store and Consumer inherite from the same model Profile, because they share many attributes (name, location, email, web page,...). Here is the Rails AR code:
class Profile << ActiveRecord::Base
# Attributes and validation rules go here.
end
class Store << Profile
end
class Consumer << Profile
end
This is the well known Single Table Inheritance (STI).
2/ In addition to STI, Store and Consumer are linked by a many to many relation:
Store has many Clients (many consumers)
A consumer is client to many stores
Because I need more attributes for this link (Store - Consumer), I have to create an extra model that will link them: Client.
Here are my final AR models:
class Profile << ActiveRecord::Base
# Attributes and validation rules go here.
end
class Store << Profile
has_many :clients
end
class Consumer << Profile
has_many :clients
end
class Client << ActiveRecord::Base
belongs_to :store
belongs_to :consumer
end
Problem
Using STI doesn't create store_id and consumer_id... we have only profile_id (because one real table Profile). So, how can I target the correct Client row having both store_id and client_id ?
Any idea how to do that? Thanks in advance.

I think you want to do is something like this. Also, I agree with Daniel Wright's comment.
class Profile << ActiveRecord::Base
belongs_to :store
belongs_to :consumer
end
class Store << ActiveRecord::Base
has_one :profile
has_many :clients
has_many :consumers, :through => :clients
end
class Consumer << ActiveRecord::Base
has_one :profile
has_many :clients
has_many :stores, :through => :clients
end
class Client << ActiveRecord::Base
belongs_to :store
belongs_to :consumer
end
But if you'd like to make it work with what you have you could do something like:
class Profile << ActiveRecord::Base
end
class Store << Profile
has_many :clients, :foreign_key => 'store_id'
has_many :consumers, :through => :clients
end
class Consumer << Profile
has_many :clients, :foreign_key => 'consumer_id'
has_many :stores, :through => :clients
end
class Client << ActiveRecord::Base
belongs_to :store
belongs_to :consumer
end

Related

One relationship with multiple models [interesting rails relations concept]

I have this app where the model User can have multiple "channels". The channels part of the app has to be easily extendable each with its own model.
I started creating a Channel model with a belongs_to User relationship with the reverse of User has_many Channel.
I was thinking about having an API similar to this:
user = User.create(name: 'John')
user.channels.create(name: 'google', account_id: '1234') # should create a GoogleChannel::Google Model
user.channels.create(name: 'facebook', account_name: 'qwerty') # should create a FacebookChannel::Facebook Model
I am playing around with having a Channel model and each channel model having a dedicated model for each of the channels (google, facebook, etc.) with a has_one relationship to that model.
Update:
I'm using mongoid with rails
I'm not sure if this works. It uses STI.
First approach: Single Table Inheritance
class User << ApplicationRecord
has_many :channels
delegate :google_channels, :facebook_channels, :twitter_channels, to: :channels
end
class Channel << ApplicationRecord
belongs_to :user
self.inheritance_column = :brand
scope :google_channels, -> { where(brand: 'Google') }
scope :facebook_channels, -> { where(brand: 'Facebook') }
scope :twitter_channels, -> { where(brand: 'Twitter') }
def self.brands
%w(Google Facebook Twitter)
end
end
class GoogleChannel << Channel; end
class FacebookChannel << Channel; end
class TwitterChannel << Channel; end
I think you can:
current_user.channels << GoogleChannel.new(name: "First Google Channel")
current_user.channels << Facebook.new(name: "Facebook")
current_user.channels << Twitter.new(name: "Tweety")
current_user.channels << GoogleChannel.new(name: "Second Google Channel")
googs = current_user.google_channels
all = current_user.channels
# etc.
All channels share the same table. If you need different attributes for each different brand, this would not be the best option.
Second approach: Polymorphic models
If you need different tables for each model (brand), you can use a polymorphic approach (not tested):
class User << ApplicationRecord
has_many :channels
has_many :google_channels, through: :channels, source: :branded_channel, source_type: 'GoogleChannel'
has_many :facebook_channels, through: :channels, source: :branded_channel, source_type: 'FacebookChannel'
end
class Channel << ApplicationRecord
belongs_to :user
belongs_to :branded_channel, polymorphic: true
end
#This channel has its own table, and can have more attributes than Channel
class GoogleChannel << ApplicationRecord
has_one :channel, as: :branded_channel
end
#This channel has its own table, and can have more attributes than Channel
class FacebookChannel << ApplicationRecord
has_one :channel, as: :branded_channel
end
goog = GoogleChannel.create(all_google_channel_params)
face = GoogleChannel.create(all_facebook_channel_params)
current_user.channels << Channel.new(branded_channel: goog)
current_user.channels << Channel.new(branded_channel: face)
I assume you want to create STI where User has many channels, so in Rails 5 you can try this:
class User < ApplicationRecord
has_many :user_channels
has_many :channels, through: :user_channels
has_many :facebook_channels, class_name: 'FacebookChannel', through: :user_channels
has_many :google_channels, class_name: 'GoogleChannel', through: :user_channels
end
class UserChannel < ApplicationRecord
# user_id
# channel_id
belongs_to :user
belongs_to :channel
belongs_to :facebook_channel, class_name: 'FacebookChannel', foreign_key: :channel_id, optional: true
belongs_to :google_channel, class_name: 'GoogleChannel', foreign_key: :channel_id, optional: true
end
class Channel < ApplicationRecord
# You need to have "type" column to be created for storing different chanels
has_many :user_channels
has_many :users, through: :user_channels
end
class FacebookChannel < Channel
has_many :user_channels
has_many :users, through: :user_channels
end
class GoogleChannel < Channel
has_many :user_channels
has_many :users, through: :user_channels
end
Now you can do current_user.facebook_channels.create(name: "My Facebook Channel") and get all FacebookChannel with current_user.facebook_channels
Basically it works like regular has_many through relationship with extra feature of STI - you store sub-model name in type column of Channel model.
Update
I'm sorry, I didn't know my suggestion is not working with MongoDB. Maybe you can simply create channel_type column in your channels table, have simple channel belongs_to user relationship and then do:
current_user.channels.create(name: "My Channel Name", channel_type: "facebook")
current_user.channels.where(channel_type: "facebook")
You could do simply current_user.channels which gives you all channels for user and then if you need you can do what ever you need with each record according to channel_type value.
You can even create some scopes in Channel model:
class Channel < ApplicationRecord
scope :facebook_channels, -> { where(channel_type: "facebook") }
end
And then you can do your desired current_user.channels.facebook_channels
channel_type column could be string or integer if you do it with enums.
In addition if you create visit column (e.g. "boolean"), you can do current_user.channels.where(visit: true) or create scope :visit_true, -> { where(visit: true) } and do something like current_user.channels.visit_true
What do you say?

has_many and belongs_to with join table

I have Users and Trucks. I want the ability to say #truck.drivers << #users and #user.truck = #truck.
The solution is simple until I want the relationship to be stored in a join table.
# tables
:users
:id
:truck_drivers
:user_id
:truck_id
:truck
:id
I've gotten it to where I can say #truck.drivers << #user and #user.trucks << #truck, but I would like to limit a user to occupy one truck at a time, for my sanity.
Is it possible? A has_many/belongs_to with a join table? Or should I try a different approach? I'm not using a third model for the join table. It's just a table. Here's what I have so far.
class User < ApplicationRecord
has_and_belongs_to_many :trucks,
join_table: :truck_drivers, # points to the table
class_name: :Truck # looks for the truck model in the table
end
class Truck < ApplicationRecord
belongs_to :company
has_and_belongs_to_many :drivers,
join_table: :truck_drivers,
class_name: :User
end
The reason I need a join table in the first place is because each User can have many Roles: Admin, Manager, Driver, Customer Service, etc. I thought it didn't make sense to add a truck_id to all the users if all the users are not going to be using trucks.
It seems like you ought to be able to do something like:
#user.trucks << #truck unless #user.trucks.any?
Yes this is a standard strategy with rails using the :through keyword.
rails documentation: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Make a model called TruckUser with truck_id and user_id
then edit your classes:
class User < ApplicationRecord
has_many :truck_users
has_many :trucks, through: :truck_users
end
class Truck < ApplicationRecord
belongs_to :company
has_many :truck_users
has_many :drivers, through: :truck_users
end
class TruckUser < ApplicationRecord
belongs_to :truck
belongs_to :user
end

Association where client orders from products. How to setup relation

I'm fiddling with rails and trying to build a small app for practicing purposes:
I want a client to order one or more products
I have a client table, a product table and last an order table which has a client_id and product_id
Now, I'm not quite sure how to set up a good relation between these table as in: client goes to product page, chooses product and saves the order.
Any help about which model should have which relation is greatly appreciated.
You can set up associations like this
Class Client < ActiveRecord::Base
has_many :orders
has_many :products,through: :orders
end
Class Product < ActiveRecord::Base
has_many :orders
has_many :clients,through: :orders
end
Class Order < ActiveRecord::Base
belongs_to :client
belongs_to :product
end
For more details,see these Guides
The association should look something like this
Class Client < ActiveRecord::Base
has_many :orders
has_many :products,through: :orders
end
Class Product < ActiveRecord::Base
has_many :orders
has_many :clients,through: :orders
end
Class Order < ActiveRecord::Base
belongs_to :client
belongs_to :product
end
You could do it like this:
#app/models/product.rb
has_many :orders
has_many :clients, through: :orders
#app/models/order.rb
belongs_to :client
has_many :order_products
has_many :products, through: :order_products
#app/models/client.rb
has_many :orders
has_many :products, through: :orders
The way to handle the creation of a new order is to set a uuid for it, and then create another join model which will handle order products. You could do this with the order model, but I felt it best to describe the basic way, as it will give you something to work from
--
uuid
We like to set uuid columns for sensitive data like orders:
#migration
add_column :orders, :uuid, :varchar, after: :id
#app/models/order.rb
before_create :set_uuid
private
def set_uuid
unless self.uuid
loop do
token = SecureRandom.hex(5)
break token unless self.class.exists?(uuid: token)
end
end
end
This means every order will have as many products as you want, accessible like this:
#user.orders.first.products #-> returns order products
--
Edit - just saw #Pavan's answer. Sorry if it's the same - hope it helps!
You have two approaches
1) set has_and_belongs_to_many between client and product model.
class Client < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :clients
end
2) set through relation between the client and production through using keyword through
class Client < ActiveRecord::Base
has_many :orders
ha_smany :products, through: orders
end
class Product < ActiveRecord::Base
has_many :orders
ha_smany :clients, through: orders
end
I am suggesting you to go with second option since you have an intermediate model.

Active record: Querying nested many to many models

I have a Store that has many Catalogs and in turn each catalog has many Products.
The Product and Catalog shares many to many relationship.
One product can belong to many catalogs and vice-verse.
So, my model definition goes like this:
class Store < ActiveRecord::Base
has_many :store_catalogs
has_many :catalogs, :through => :store_catalogs
end
class StoreCatalog < ActiveRecord::Base
belongs_to :store
belongs_to :catalog
end
class Catalog < ActiveRecord::Base
has_many :store_catalogs
has_many :stores, :through => :store_catalogs
has_and_belongs_to_many :product_set, :class_name => "Product"
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :catalogs
end
Now, I would like to access all the products that belongs to a store.
How do I link those together so that I get that? please suggest.
I am trying out various combinations from the rails console to pull the data, not sure if the console itself limits the relation based querying by any chance(though i would like to believe it doesn't).
I think this should solve your problem
class Store < ActiveRecord::Base
has_many :catalogs
has_many :products, :through => :catalogs
end
class Catalog < ActiveRecord::Base
belongs_to :store
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :catalogs
end
And then you just need a table in your database called catalogs_products with catalog_id and product_id to link the has_and_belongs_to_many association.
Then in order to get all the products from a store just do
Store.find(id).products

Retrieve the Active Record Association name between two classes\models

I am using Ruby on Rails 3.0.7 and I would like to know how to retrieve the Active Record Association name between two classes\models.
That is, I have two models
class User < ActiveRecord::Base
has_many :accounts
end
class Account < ActiveRecord::Base
belongs_to :users
end
and I would like to retrieve (on runtime) them association name, in this case accounts and users strings.
Is it possible? If so, how can I do that?
UPDATE
If I have more association statements in User and Account classes (see the below example), how can I retrieve exactly the User Account association name?
class User < ActiveRecord::Base
has_many :accounts
has_many :articles
has_many :comments
end
class Account < ActiveRecord::Base
belongs_to :users
has_many :articles
belongs_to :authorization
end
?
User.reflect_on_all_associations.each do |assoc|
puts "#{assoc.macro} #{assoc.name}"
end
#=> "has_many accounts"
UPD
User.reflect_on_all_associations.select{|a| a.class_name == "Account"}.each do |assoc|
puts "#{assoc.macro} #{assoc.name}"
end
#=> "has_many accounts"

Resources