Rails Association: 2 occurrences of same model - ruby-on-rails

I have a Consultation model that has a post_consultant and a consultant. Both post_consultant and consultant are references to the Employee model. So you could say:
Model
Class Consultation < ActiveRecord::Base
has_one :employee # for consultant
has_one :employee # for post_consultant
end
Migration
create_table "consultations", force: :cascade do |t|
t.boolean "showed_up"
t.boolean "signed_up"
t.integer "client_id"
t.integer "consultant_id"
t.integer "post_consultant_id"
end
How am I supposed to write that?
Correct Model:
class Consultation < ActiveRecord::Base
belongs_to :consultant, class_name: "Employee", foreign_key: "consultant_id"
belongs_to :post_consultant, class_name: "Employee", foreign_key: "post_consultant_id"
end

Class Consultation < ActiveRecord::Base
belongs_to :consultant, :class_name => "Employee", :foreign_key=> "consultant_id", dependent: :destroy
belongs_to :post_consultant, :class_name=>"Employee", :foreign_key=> "post_consultant_id", dependent: :destroy
end

You can define multiple relation referring to same model.
Class Consultation < ActiveRecord::Base
has_one :consultant, class_name: 'Employee', foreign_key: :consultant_id
has_one :post_consultant, class_name: 'Employee', foreign_key: :post_consultant_id
end
Note: mention whichever foreign key you are using for each association using syntax above.

Related

Rails: How to handle associations among namespaced models?

I am trying to figure out what's the best way to handle namespaced models. Here's the models that i have in my project:
class Member < ApplicationRecord
has_one :ledger, inverse_of: :member, class_name: "Member::Ledger", dependent: :destroy
has_many :ledger_entries, through: :ledger
end
class Member::Ledger < ApplicationRecord
belongs_to :member, inverse_of: :ledger
has_many :ledger_entries, foreign_key: "member_ledger_id", dependent: :destroy
end
class Member::LedgerEntry < ApplicationRecord
belongs_to :ledger, foreign_key: "member_ledger_id"
end
And here's how my migrations files look like:
create_table :members do |t|
t.timestamps
end
create_table :member_ledgers do |t|
t.references :member, foreign_key: true, null: false, index: { unique: true }
t.timestamps
end
create_table :member_ledger_entries do |t|
t.references :member_ledger, foreign_key: true, null: false
t.timestamps
end
So I have few questions here:
Are migration files correct? I mean should i have member_ledger_id in the member_ledger_entries table or just ledger_id?
Are associations defined in a correct way? Even though this works but i am not sure this is how we are supposed to proceed.
I am using ruby-2.5.1 and rails-5.2.0.
Any help would be appreciated. Thanks in advance !!
Perhaps your associations could look more like:
class Member < ApplicationRecord
has_one :member_ledger, inverse_of: :member, dependent: :destroy
has_many :member_ledger_entries, through: :member_ledger
end
class Member::Ledger < ApplicationRecord
belongs_to :member, inverse_of: :member_ledger
has_many :member_ledger_entries, dependent: :destroy
end
class Member::LedgerEntry < ApplicationRecord
belongs_to :member_ledger
end

has_many through - Notes sender and receiver

I'm working on a rails app which has three models.
class User < ApplicationRecord; end
class Share < ApplicationRecord; end
class Note < ApplicationRecord; end
create_table :users do |t|
t.timestamps
end
create_table :notes do |t|
t.integer 'user_id'
t.text 'title'
t.text 'short_description'
t.string 'name'
t.timestamps
end
create_table :shares do |t|
t.integer 'user_id'
t.integer 'receiver_id'
t.integer 'note_id'
t.timestamps
end
How can I create associations between them so, I can get
Notes which are shared by User A.
Notes which are received by User A.
Notes which are created by User A.
#Mehmet Adil İstikbal gives part of the answer so I'll try to complete it.
This is another way to do it using only associations :
class User < ApplicationRecord
has_many :created_notes, class_name: 'Note', foreign_key: :user_id
has_many :received_shares, foreign_key: :receiver_id, class_name: 'Share'
has_many :received_notes, through: :received_shares, source: :note
has_many :shares
has_many :shared_notes, through: :shares, source: :note
end
class Share < ApplicationRecord
# Optional
belongs_to :creator, class_name: 'User', foreign_key: :user_id
belongs_to :receiver, class_name: 'User', foreign_key: :receiver_id
# Mandatory
belongs_to :note
end
class Note < ApplicationRecord ; end
user_a = User.first
user_a.shared_notes
user_a.received_notes
user_a.created_notes
If you choose #Mehmet Adil İstikbal answer, please make sure to transform
user.shares.each {|share| share.note} to user.shares.map(&:note) (Use map and not each)
My answer uses has_many through association which allows you to go "through" join table.
In user model you can do like this:
has_many :shares, foreign_key: 'user_id', class_name: 'Share', dependent: :destroy
has_many :receives, foreign_key: 'receiver_id', class_name: 'Share', dependent: :destroy
and you can call like this:
User.first.shares.each {|share| share.note}
This will get all shares with first users id and all of their notes.
For receiver :
User.first.receives.each {|share| share.note}
In your share model you can also specify the opposite connection like this:
belongs_to :sender, foreign_key: 'user_id', class_name: 'User'
belongs_to :receiver, foreign_key: 'receiver_id', class_name: 'User'
With this you can call:
Share.first.receiver this will get you to user that receives this post
And for the notes which are created by user you can call:
User.first.notes
You may want to delete those dependents in order to your project.
Hope it helps

Use custom id "foreign key name" for use it like object

Title isn't explicit, but I didn't know how to explain my problem in few words.
I've a Sale model with this fields:
create_table "sales", force: true do |t|
t.string "title"
...
t.integer "seller_id"
t.integer "buyer_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "category_id"
...
end
In a view, I'm able to do that with Category:
<td><%= link_to sale.category.label, category_path(sale.category) %></td>
Cause I think Rails convention know category_id is related to an Category object
So, I want to do exactly the same for seller_id and buyer_id which are both User.
Unfortunally, I fall on error when I try:
<td><%= sale.seller.first_name %></td>
output:
undefined method `seller' for Sale
There, how my Models are linked:
User:
class User < ActiveRecord::Base
has_many :offers
has_many :sales
end
Sale:
class Sale < ActiveRecord::Base
belongs_to :category
belongs_to :user, foreign_key: "seller_id"
belongs_to :user, foreign_key: "buyer_id"
EDIT:
Yeah, it's make more sense. I had misunderstood the documentation about that.
But I've still an error:
undefined method `first_name' for nil:NilClass
I think it's cause Rails didn't find the User... But I've a good value in seller_id...
EDIT 2:
Still not working with:
User model:
class User < ActiveRecord::Base
has_many :offers
has_many :sales, foreign_key: :seller_id
has_many :sales, foreign_key: :buyer_id
end
Sale model:
class Sale < ActiveRecord::Base
belongs_to :category
belongs_to :seller, class_name: :User
belongs_to :buyer, class_name: :User
end
Same error on :
<td><%= sale.seller.first_name %></td>
output
undefined method `first_name' for nil:NilClass
You need to tweak your associations in your Sale model.
This should work
Class Sale < ActiveRecord::Base
belongs_to :category
belongs_to :seller, class_name: "User",foreign_key: "seller_id",
belongs_to :buyer, class_name: "User",foreign_key: "buyer_id"
end
I believe the models should seem as follows:
class User < ActiveRecord::Base
has_many :sales, foreign_key: :seller_id
has_many :buys, foreign_key: :buyer_id
end
class Sale < ActiveRecord::Base
belongs_to :seller, class_name: :User
belongs_to :buyer, class_name: :User
end
So when you use belongs_to you should just specify class_name, but in class that contains has_many related to the specific belongs_to you should explicitly denote the name of field in the belongs_to class.

Rails associations in one table (belongs_to :region, has_many :regions)

I'm trying to create simple geo-model with tree-structure with Rails4. Every region has one parent region and can have many children regions.
class Region < ActiveRecord::Base
has_many :regions, belongs_to :region, dependent: :destroy
end
Schema:
create_table "regions", force: true do |t|
t.string "name"
t.string "description"
t.integer "region_id"
t.datetime "created_at"
t.datetime "updated_at"
end
Unfortunatelly, such code is not working. What should i do?
I think, you are looking for a self join relationship. Try this :
class Region < ActiveRecord::Base
has_many :child_regions, class_name "Region", foreign_key: "parent_id" dependent: :destroy
belongs_to :parent, class_name: "Region"
end
You should have a parent_id in your schema as well. Thanks
I assume that Rails4 works just as Rails3 in this case:
class Region < ActiveRecord::Base
has_many :regions, dependent: :destroy
belongs_to :region
end
has_many and belongs_to are class/singleton methods of Region. Aa such you cannot use one of them as a parameter to the other method.
class Region < ActiveRecord::Base
has_many :regions, dependent: :destroy
belongs_to :region
end
Of course you also need region_id integer column in your regions table.

How to update my Users/Products association

I'm trying to create an app to share or give products. So I have two models : User and Product.
A user can have many products, as an owner or as a borrower. A product has only one owner and only one borrower.
First I did something like that :
> rails generate model User name:string
class User
has_many :owned_products, class_name: "Product", foreign_key: "owner_id"
has_many :borrowed_products, class_name: "Product", foreign_key: "borrower_id"
end
> rails generate model Product name:string owner_id:integer borrower_id:integer
class Product
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :borrower, class_name: "User", foreign_key: "borrower_id"
end
I added in my Product controller a security filter that enable the update method only for product's owner. But when I want to change the product's borrower, I have some kind of a problem, because the borrower is never the owner, so the product can not be updated.
So now I'm wondering if I should not take the foreign_key out of my products model, in order to dissociate the update action of a user on his own product, and the update action of a user to borrow a product that don't belongs to him...
> rails generate model User name:string
class User
has_many :properties
has_many :loans
has_many :owned_products, through: :properties
has_many :borrowed_products, through: :loans
end
> rails generate model Property owner_id:integer owned_product_id:integer
class Property
belongs_to :owner, class_name: "User", foreign_key: "user_id"
belongs_to :owned_product, class_name: "Product", foreign_key: "product_id"
end
> rails generate model Loan borrower_id:integer borrowed_product_id:integer
class Loan
belongs_to :borrower, class_name: "User", foreign_key: "user_id"
belongs_to :borrowed_product, class_name: "Product", foreign_key: "product_id"
end
> rails generate model Product name:string
class Product
has_one :property
has_one :loan
has_one :owner, through: :property
has_one :borrower, through: :loan
end
What do you think about it ?
Since borrowed products and owned products are the same type of object with the same list of attributes, but differ only in behavior, I would use single table inheritance for Product.
Migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
# ...
t.timestamps
end
end
end
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.integer :ownerable_id
t.string :ownerable_type
# ...
t.timestamps
end
end
end
Models:
class User < ActiveRecord::Base
has_many :products, :as => :ownerable
end
class Product < ActiveRecord::Base
belongs_to :user, :polymorphic => true
end
class OwnedProduct < Product
end
class BorrowedProduct < Product
end
The benefit of this approach is that you can just define the appropriate behavior in each model without asking it if it's "owned" or "borrowed." Just tell your models what to do and leave the decisions up to each object to do the right thing.

Resources