How to use :inverse_of with multiple associations? - ruby-on-rails

Does anyone know what I should put in the empty fields below to make this relationship work out of the box? I am very close to making this work as all associations work flawlessly. The only issue is that I cannot save a user with fruits attached to it as, currently, there is no :inverse_of.
I need the :inverse_of pointing in the right direction so that I can save a user with fruits instead of having to save the user first and then attach fruits to it later.
Thank you!
UPDATED AFTER COMMENTS:
The User model:
class User < ApplicationRecord
has_many :bought_fruits_users, -> { bought },
class_name: 'FruitsUser', inverse_of: :buyer
has_many :bought_fruits, through: :bought_fruits_users,
class_name: 'Fruit', source: :bought_fruit
has_many :sold_fruits_users, -> { sold },
class_name: 'FruitsUser', inverse_of: :seller
has_many :sold_fruits, through: :sold_fruits_users,
class_name: 'Fruit', source: :sold_fruit
end
The middle-table model:
class FruitsUser < ApplicationRecord
belongs_to :seller, foreign_key: :user_id,
class_name: 'User', inverse_of: :sold_fruits_users
belongs_to :buyer, foreign_key: :user_id,
class_name: 'User', inverse_of: :bought_fruits_users
belongs_to :bought_fruit, foreign_key: :fruit_id,
class_name: 'Fruit', inverse_of: :buying_fruits_users
belongs_to :sold_fruit, foreign_key: :fruit_id,
class_name: 'Fruit', inverse_of: :selling_fruits_users
scope :bought, -> { where(type_of: 'bought') }
scope :sold, -> { where(type_of: 'sold') }
end
The Fruit model:
class Fruit < ApplicationRecord
has_many :buying_fruits_users, -> { bought },
class_name: 'FruitsUser', inverse_of: :bought_fruit
has_many :buying_users, through: :bought_fruits_users,
class_name: 'User', source: :buyer
has_many :selling_fruits_users, -> { sold },
class_name: 'FruitsUser', inverse_of: :sold_fruit
has_many :selling_users, through: :sold_fruits_users,
class_name: 'User', source: :seller
end
Still can't save it:
u = User.new [OK]
u.needs << Fruit.sample [OK]
u.valid? [false]
u.errors [:bought_fruits_users=>["is invalid"]]

Related

Converting a has_one association to has_many

Wondering about a relationship I have and not sure wheter this is due to cause some issues in the future.
I have the following relationships with Users and Leases.
class User < ApplicationRecord
has_one :lease, foreign_key: "tenant_id"
has_many :leases, foreign_key: "landlord_id"
end
and
class Lease < ApplicationRecord
belongs_to :tenant, class_name: "User"
belongs_to :landlord, class_name: "User"
end
and I'm trying to convert the relationship with the tenant and the lease to has_many, but I don't know how to approach this the right way.
I got this to work with
class User < ApplicationRecord
has_many :leases_as_landlord, class_name: "Lease", foreign_key: "tenant_id"
has_many :leases_as_tenant, class_name: "Lease", foreign_key: "landlord_id"
end
and
class Lease < ApplicationRecord
belongs_to :tenant, class_name: "User", inverse_of: :leases_as_tenant
belongs_to :landlord, class_name: "User", inverse_of: :leases_as_landlord
end
but I don't like calling User.leases_as_landlord and User.leases_as_tenant. What I would like to do is just call User.leases to return the leases in which the User is either the landlord or the tenant.
You can add instance method:
class User < ApplicationRecord
has_many :leases_as_landlord, class_name: "Lease", foreign_key: "tenant_id"
has_many :leases_as_tenant, class_name: "Lease", foreign_key: "landlord_id"
def leases
leases_as_landlord.or(leases_as_tenant)
end
end
It will also return ActiveRecord_AssociationRelation and you can chain other ActiveRecord method on it.
Also I would recommend to follow Rails Convention and name your has_many associations in the plural.
class User < ApplicationRecord
has_many :landlord_leases, class_name: 'Lease', foreign_key: :tenant_id
has_many :tenant_leases, class_name: 'Lease', foreign_key: :landlord_id
def leases
landlord_leases.or(tenant_leases)
end
end

Self-Referential Has Many Through with Custom Foreign Keys in Rails

I have a User model and a relationship table called ParentsChildren.
I'm trying to create two relationships on the User model so that User#children returns all of a users children and User#parents returns all of a users parents.
I've managed to get this working before, but I'm doing something wrong right this time, and I'm not sure what it is exactly.
class ParentsChildren < ApplicationRecord
self.table_name = 'parents_children'
belongs_to :parent_user, class_name: 'User'
belongs_to :child_user, class_name: 'User'
end
class User
has_many :parent_relationships, class_name: 'ParentsChildren', foreign_key: :parent_user_id
has_many :child_relationships, class_name: 'ParentsChildren', foreign_key: :child_user_id
has_many :children, through: :parent_relationships, class_name: 'User', source: :child_user
has_many :parents, through: :child_relationships, class_name: 'User', source: :parent_user
end
# => uninitialized constant ParentsChildren::ChildUser
Figured it out. The key was to drop 'User' as the class name for has_many :parents and has_many :users. It's inferred through the given sources.
class User
has_many :parent_relationships, foreign_key: :child_user_id,
class_name: 'ParentsChildren'
has_many :children, through: :parent_relationships,
source: :parent_user
has_many :child_relationships, foreign_key: :parent_user_id,
class_name: 'ParentsChildren'
has_many :parents, through: :child_relationships,
source: :child_user
end

How to create this `through`` association?

Organization and Link are associated through Node.
Organization:
has_many :nodes
has_many :links, through: :nodes, source: :where_first_links
Node:
belongs_to :organization
has_many :where_first_links, class_name: "Link",
foreign_key: "first_node_id"
has_many :where_second_links, class_name: "Link",
foreign_key: "second_node_id"
Link:
belongs_to :first_node, class_name: "Node"
belongs_to :second_node, class_name: "Node"
Question:: How can I associate Link back to Organization? I tried the line below but that does not seem to work (ArgumentError: Unknown key: :through.):
belongs_to :organization,
through: :first_node,
source: :where_first_links,
inverse_of: :links
belongs_to association not support through key
you should use has_one association
has_one :first_node_organization,
through: :first_node,
class_name: 'Organization',
source: :organization
Use has_one instead of belongs_to.
class Link < ActiveRecord::Base
belongs_to :first_node, class_name: "Node"
belongs_to :second_node, class_name: "Node"
has_one :organization, through: :first_node
end

No such column for attribute in a join table

I'm trying to create an app where a user chooses volunteers to complete their task. The way that volunteers are considered participants is through the selected boolean attribute placed on the TaskVolunteer join table. Unfortunately when I try to find the participants of a particular class I get the following error:
task = Task.create
task.participants
SQLite3::SQLException: no such column: users.selected
Models
class User < ActiveRecord::Base
has_many :owned_tasks, class_name: "Task", foreign_key: :owner_id
has_many :task_volunteers, as: :volunteer
has_many :volunteered_tasks, through: :task_volunteers
end
class TaskVolunteer < ActiveRecord::Base
# task_id, volunteer_id, selected (boolean)
belongs_to :task
belongs_to :volunteer, class_name: "User", foreign_key: :volunteer_id
end
class Task < ActiveRecord::Base
# owner_id
has_many :task_volunteers
has_many :volunteers, through: :task_volunteers, source: :volunteer
has_many :participants, -> {where(selected: true)}, through: :task_volunteers, source: :volunteer
belongs_to :owner, class_name: "User"
end
The error is caused by a faulty foreign_key option in TaskVolunteer.
belongs_to :volunteer, class_name: "User", foreign_key: :volunteer_id
foreign_key here refers to the column on the users table not on tasks_volunteers. You can just remove the foreign key option.
class TaskVolunteer < ActiveRecord::Base
# task_id, volunteer_id, selected (boolean)
belongs_to :task
belongs_to :volunteer, class_name: "User"
end
Added
I have to say though by altering the naming a bit and using an enum to denote status you could cut the code and cognitive complexity quite dramatically.
class User < ActiveRecord::Base
has_many :participations, foreign_key: :participant_id
has_many :owned_tasks, class_name: "Task", as: :owner
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :participations
has_many :participants, through: :participations, source: :participant
# Dynamically generates relations such as 'selected_participants'
Participation.statuses.keys.each do |status|
has_many "#{status}_participants".to_sym,
-> { where(participations: { status: status.to_sym }) },
through: :participations,
source: :participant
end
end
class Participation < ActiveRecord::Base
belongs_to :task
belongs_to :participant, class_name: "User"
enum status: [:interested, :selected]
end
The enum macro gives you stuff like:
user.participations.selected
participation.selected?

Custom method or association for chained model associations

I have three models : User, Product and Transaction.
Here are the associations :
app/models/transaction.rb
# A transaction has a `current` boolean that is true when the transaction is currently happening, and nil else.
belongs_to :seeker, class_name: "User", foreign_key: "seeker_id"
belongs_to :product
app/models/user.rb
has_many :owned_products, class_name: "Product",
foreign_key: "owner_id",
dependent: :destroy
has_many :transactions, foreign_key: "seeker_id",
dependent: :destroy
has_many :requested_products, through: :transactions, source: :product
has_many :active_transactions, -> { where current: true },
class_name: 'Transaction',
foreign_key: "seeker_id"
has_many :borrowed_products, through: :active_transactions, source: :product
app/models/product.rb
belongs_to :owner, class_name: "User",
foreign_key: "owner_id"
has_many :transactions, dependent: :destroy
has_many :seekers, through: :transactions,
source: :seeker
has_one :active_transaction, -> { where current: true },
class_name: 'Transaction'
has_one :borrower, through: :active_transaction,
source: :seeker
I want to create a method that allows me to do the following :
user.owned_products.available # returns every product owned by the user that has a transaction with current:true.
user.owned_products.lended # returns every product owned by the user that has no transaction with current.true
Is this possible ? If not, I would do an association link like user.available_products and user.lended_products but I don't know how, because I must use conditions from both models in order to make an association in a third, like this :
app/models/user.rb
has_many :available_products, -> { where borrower: nil },
class_name: "Product",
foreign_key: "owner_id"
And I get this error message :
ActionView::Template::Error:
SQLite3::SQLException: no such column: products.borrower: SELECT COUNT(*) FROM "products" WHERE "products"."owner_id" = ? AND "products"."borrower" IS NULL
Any hint ?
Create a scope
scope :available, where(:current => true).joins(:transactions)
now you can say
user.owned_products.available
This is not tested. But this will give you an idea of how to go ahead.
Here is a reference for scopes.

Resources