Can rails have multiple relations to a model in the same model? - ruby-on-rails

I have a similar_merchants model where I store merchants which are similar to other merchants.
Here are the fields in the table:
merchant_id
related_merchant_id
How do I write the relations in this case?
Here is what I have right now:
class SimilarMerchant < ActiveRecord::Base
# relationships
belongs_to :merchant
belongs_to :merchant, :foreign_key => 'related_merchant_id'
end

class SimilarMerchant < ActiveRecord::Base
# relationships
belongs_to :merchant
belongs_to :related_merchant, :foreign_key => 'related_merchant_id', :class_name => "Merchant"
end
This will do. You can call like this:
similar_merchant = SimilarMerchant.find(1)
similar_merchant.merchant
=> # Merchant record
similar_merchant.related_merchant
=> # Related Merchant record

This should be enough:
class SimilarMerchant < ActiveRecord::Base
set_primary_key 'merchant_id'
belongs_to :merchant, :foreign_key => 'related_merchant_id'
end
As per the documentation, the foreign_key is guesssed to be the name of the association plus an '_id' suffix.

Related

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

Rails association through a SHA1 rather than id

I'm trying to link two tables on a SHA1 rather than doing a find for the SHA1 across tables, and linking the via id.
My models are
class Dataset < ActiveRecord::Base
belongs_to :dataset_hash
end
class DatasetHash < ActiveRecord::Base
has_many :datasets
end
I tried linking using
has_many :datasets, :finder_sql => 'Select datasets.* FROM datasets LEFT JOIN dataset_hashes ON datasets.dataset_hash=dataset_hashes.hash WHERE dataset_hashes.hash=#{dataset.dataset_hash}'
but I just get an error of
DatasetHash(#...) expected, got String(#...)
You can use :foreign_key and :primary_key available in belongs_to and has_many:
class Dataset < ActiveRecord::Base
belongs_to :dataset_hash, :primary_key => "dataset_hash", :foreign_key => "hash"
end
class DatasetHash < ActiveRecord::Base
has_many :datasets, :primary_key => "hash", :foreign_key => "dataset_hash"
end

Rails relationships don't work

I'm using Rails 3.0.1/Ruby 1.9.2
I have two tables: Product and Category.
These are the models:
class Product < ActiveRecord::Base
belongs_to :parent_category, :class_name => "Category"
end
class Category < ActiveRecord::Base
has_many :products
end
So I want to access the category of the product by calling product.parent_category, and get all products from a certain category by calling category.products.
But this doesn't work.
Rails raises an exception when I do category.products
column products.category_id does not exist
It tries to find the category_id column, but I have a parent_category_id column in my table and I want to use it.
How can I fix this?
Try this:
class Product < ActiveRecord::Base
belongs_to :parent_category, :class_name => "Category"
end
class Category < ActiveRecord::Base
has_many :products, :foreign_key => :parent_category_id
end
class Product < ActiveRecord::Base
belongs_to :parent_category, :class_name => "Category", :foreign_key => "parent_category_id"
end

Complex joins with legacy tables in Rails

I'm writing fairly simple web interface for invoice/customer/contract database.
I would like to have the following structure for my models:
class Invoice < ActiveRecord::Base
set_table_name 't10_invoices'
set_primary_key 'id_invoice'
has_one :client
end
class Client < ActiveRecord::Base
set_table_name 't20_clients'
set_primary_key 'id_client'
has_many :invoices
end
The problem is that I have the following database structure:
Tables:
t10_invoices:
id_invoice - primary_key
id_contract - foreign_key
t12_contracts:
id_contract - primary_key
t15_contracts_clients
id_client - foreign_key
id_contract - foreign_key
t20_clients
id_client - primary_key
There is 1-1 relation between t20_clients and t12_contracts through t15_contracts_clients
and there is 1-n relation between t20_clients and t10_invoices.
What is the difference between :foreign_key and :association_foreign_key?
The problem is that I cannot modify the structure of the database and I'm quite lost in the redefining the table names, foreign keys, join_foreign_keys etc...
I would like a simple way to find clients of invoice in normal Rails way Invoice.find(:first).client
I would appreciate any suggestions and help.
Create a ContractClient Join Model to correspond to the t15_contracts_clients table. This specifies the table name and uses belongs_to associations for the 2 foreign keys:
class ContractClient < ActiveRecord::Base
set_table_name 't15_contracts_clients'
belongs_to :client, :foreign_key => :id_client
belongs_to :contract, :foreign_key => :id_contract
end
Specify the table and primary key for Client:
class Client < ActiveRecord::Base
set_table_name 't20_clients'
set_primary_key 'id_client'
end
Use a belongs_to :contract_client on Invoice and then a has_one :through to associate the client through the ContractClient:
class Invoice < ActiveRecord::Base
set_table_name 't10_invoices'
set_primary_key 'id_invoice'
belongs_to :contract_client, :foreign_key => :id_contract
has_one :client, :through => :contract_client
end
Invoice.find(:first).client should then behave as expected.
You have a couple of oddities in the model. It is not set up in such a way that client has_one contract. It has a join table in the middle which allows for multiple contracts per client. Since you can't change the tables, it's best to just enforce whatever relationships there are in code. Since that table apparently does not have a primary key, you probably need to use has_and_belongs_to_many. In that relationship type, :association_foreign_key is for the "other" class, and :foreign_key is for the class you are in.
class Invoice < ActiveRecord::Base
set_table_name 't10_invoices'
set_primary_key 'id_invoice'
belongs_to :contract, :foreign_key => 'id_contract'
has_one :client, :through => :contract
end
class Contract
set_table_name 't12_contracts'
set_primary_key 'id_contract'
has_and_belongs_to_many :clients,
:join_table => 't15_contracts_clients'
:foreign_key => 'id_contract',
:association_foreign_key => 'id_client'
has_many :invoices, :foreign_key => 'id_contract'
end
class Client < ActiveRecord::Base
set_table_name 't20_clients'
set_primary_key 'id_client'
has_and_belongs_to_many :clients,
:join_table => 't15_contracts_clients'
:foreign_key => 'id_client',
:association_foreign_key => 'id_contract'
has_many :invoices, :through => :contracts
end
You then need to invoke
Invoice.find(:first).client
Have you considered creating a database view?

Multiple Associations to Same Model

I have two classes that I would like to specify as follows:
class Club < ActiveRecord::Base
belongs_to :president, :class_name => "Person", :foreign_key => "president_id"
belongs_to :vice_president,
:class_name => "Person",
:foreign_key => "vice_president_id"
end
class Person < ActiveRecord::Base
has_one :club, :conditions =>
['president_id = ? OR vice_president_id = ?', '#{self.id}', '#{self.id}']
end
This doesn't work and gives me an error when trying to get the club association from the person object. The error is because is looking for person_id in the club table when I looked at the SQL. I can get around it by declaring multiple has_one associations, but feel like this is the improper way of doing it.
A person can only be the President or Vice President of one club.
Anyone able to offer a little bit of advice on this issue, I would be very appreciative.
Your has_one condition will never work in Rails, as far as I know.
You need one explicit has_one or belongs_to or has_many per "link", on both tables. So if you have two "links", you need two has_one and two belongs_to. That is how it works.
Secondly, I think you should reconsider your models. The way you are doing it, one person can not be the president of a club and an employee, at the same time. Or be the president of two clubs. Even if you don't have these right now, they can come in the future - it is easier to stay flexible right now.
A flexible way of doing this is using a has_many :through with an intermediate table that specifies the role. In other words:
# The memberships table has a person_id, club_id and role_id, all integers
class Membership < ActiveRecord::Base
belongs_to :club
belongs_to :person
validates_presence_of :role_id
validates_numericality_of :role_id
end
class Club < ActiveRecord::Base
has_many :memberships, :dependent => :delete_all
has_many :people, :through => :memberships
end
class Person < ActiveRecord::Base
has_many :memberships, :dependent => :delete_all
has_many :clubs, :through => :memberships
end
Now, assuming that role_id=0 means employee, role_id=1 means president, and role_id=2 means vice_president, you can use it like this:
tyler = Person.find(1) # person_id is 1
other = Person.find(2) # person_id is 2
c = Club.find(1) # club_id is 1
tyler.clubs # returns all the clubs this person is "member" of
c.people # returns all the "members" of this club, no matter their role
#make tyler the president of c
tyler.memberships.create(:club_id => 1, :role_id => 1)
#make other the vicepresident of c
#but using c.memberships instead of other.memberships (works both ways)
c.memberships.create(:person_id => 2, :role_id => 1)
#find the (first) president of c
c.memberships.find_by_role_id(1).person
#find the (first) vicepresident of c
c.memberships.find_by_role_id(2).person
#find all the employees of c
c.memberships.find_all_by_role_id(0).collect { |m| m.person }
#find all the clubs of which tyler is president
tyler.memberships.find_all_by_role_id(1).collect { |m| m.club }
Additional notes:
You could complement this with a roles table and model. Roles would have just a a name, roles would have_many relationships and memberships would belong_to role. Or, you could define methods in memberships for getting the role name (if 0, it returns "employee", if 1, "president", etc
You can add validations on memberhips so no more than 1 person can be made president of a given club, or the same employee on the same club twice. Later on, if you start getting "exceptional cases" in which a person needs to be in two places, you will just have to adapt your validations.
I think your associations are the wrong way around. Your way it is hard to assign a president or vice president.
I would do it like this:
class Club < ActiveRecord::Base
has_one :president,
:class_name => "Person",
:foreign_key => 'president_club_id'
has_one :vice_president,
:class_name => "Person",
:foreign_key => 'vice_president_club_id'
end
class Person < ActiveRecord::Base
belongs_to :club
end
Now you can assign the roles like this:
club.president = Person.create(:name => 'Tom')
club.vice_president = Person.create(:name => 'Andrew')
I suggest you introduce a new model called role. Then have the following:
class Club
has_many :roles
def president
end
def vice_president
end
end
class Person
belongs_to :role
end
class Role
has_one :person
belongs_to :club
end
This is the classic use case for polymorphic associations.
Here is the link:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Something like..
class Club < ActiveRecord::Base
belongs_to :person, :polymorphic => true
class Person < ActiveRecord::Base
has_one :club, :as => :position

Resources