Model a fork relationship within ActiveRecord - ruby-on-rails

What does the ActiveRecord::Migration and ActiveRecord::Base look like for a class that references itself. I'm modeling an object that "forks" off an existing record and stores that relation in a :source field. That :source field will contain the primary_key :id of it's parent.

ActiveRecord doesn't include a "predefined" relation of this type, but you can define it yourself using the has_many and belongs_to helpers. You would need to add a foreign key, e.g. my_parent_id to the model (I'll call it Thing):
rails g migration AddMyParentIdToThings my_parent:references
Then you would need to define the relation specifying the foreign key and class names:
class Thing < ActiveRecord::Base
belongs_to :parent_thing, class_name: "Thing", foreign_key: :my_parent_id
has_many :child_things, class_name: "Thing", foreign_key: :my_parent_id
end
You can omit the :foreign_key option on the belongs_to (not the has_many) if the foreign key matches the relation name with an appended "_id" e.g.:
belongs_to :my_parent, class_name: "Thing"

When I had to do something like that I like to think of it as a model that links to itself.
In your migration file you just would add parent_id as as integer to that table/model
class Category < ActiveRecord::Base
belongs_to :parent, :class_name => "Category", :foreign_key => :parent_id
has_many :children, :class_name => "Category", :foreign_key => :parent_id
end

Related

Has_many through with multiple relationships between the entities

I have these model definitions:
class Property < ActiveRecord::Base
belongs_to :user
has_many :users_favourites_properties, class_name: 'UsersFavouritesProperties', dependent: :destroy, :foreign_key => :property_id
has_many :favourites, :class_name => "User", through: :users_favourites_properties
end
class User < ActiveRecord::Base
has_many :properties
has_many :users_favourites_properties, class_name: 'UsersFavouritesProperties', dependent: :destroy, :foreign_key => :user_id
has_many :favourites, class_name: "Property", through: :users_favourites_properties
end
class UsersFavouritesProperties < ActiveRecord::Base
self.table_name = "favourites"
belongs_to :favourites, class_name: 'User', :foreign_key => :user_id, :primary_key => :id
belongs_to :favourites, class_name: 'Property', :foreign_key => :property_id, :primary_key => :id, counter_cache: :users_count
end
When I execute:
current_user.favourites << property
The UsersFavouritesProperties object created has the same user_id and property_id because the user_id is beign setted with the property_id.
Do you know what is happening?
You seem to misunderstand what the association name is for.
Each association method call (like has_many) adds certain methods to a model that allow for easy lookup for associated objects.
The name of association means (in scope of a model) "what is it for me". A symbol you specify first is an association name, and it is used (among other things) as a name of a method that you will use to access that association.
By defining an association named favourites once, you've defined the methods for UsersFavouritesProperties. The second time you define an association with the same name, some of the first one is redefined and crazy things can happen.
So, following that "what is it for me" principle, you can figure that a User associated with a certain UsersFavouritesProperty is UsersFavouritesProperty's user, and the resulting association, that you have:
belongs_to :favourites, class_name: 'User', :foreign_key => :user_id, :primary_key => :id
should be more like simple:
belongs_to :user
The rest is not even needed, as it follows the Rails' convention: class name and foreign key can be derived from association name in standard ways (capitalize for class name, add _id for foreign key), primary key is default.
I strongly recommend you read the Rails Style Guide and let ActiveRecord guess the most you can provide.
These lines
belongs_to :favourites, ...
belongs_to :favourites, ...
Should be
belongs_to :user, ...
belongs_to :property, ...
You can't use two keys for belongs_to

Incorrect self join model example on guides.rubyonrails.org

I feel the following self join model example given on http://guides.rubyonrails.org/association_basics.html#self-joins is incorrect.
class Employee < ActiveRecord::Base
has_many :subordinates, :class_name => "Employee",
:foreign_key => "manager_id"
belongs_to :manager, :class_name => "Employee"
end
I feel it should be as following. Can you please suggest which one is correct and why?
class Employee < ActiveRecord::Base
has_many :subordinates, :class_name => "Employee",
belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id"
end
My rationale: The model bearing belongs_to relation carries the foreign_key for the model it references to.
The example is correct.
The "convention over configuration" mantra applies here, you only need to specify what the foreign key if it is not "#{name_of_association}_id"
Therefore belongs_to :manager, :class_name => "Employee" implies the foreign key is manager_id
However has_many :subordinates, :class_name => "Employee" assumes subordinates_id to be the childrens' foreign key, which is why if must be defined. The definition of :foreign_key in a has_many will be for the child and will join against the current model's id.
you're correct. That is how it should be.
However in that sort of situation i'd use acts_as_tree or similar, to build hierachy. allows you to call things such as Employee.first.descendants to find all who work for that person.

ror - include foreign key at both ends of has_many and belongs_to?

I'm inheriting code which has:
class Graphic < ActiveRecord::Base
has_many :comments, :foreign_key => 'asset_id',
:conditions => 'asset_type_id = 5',
:order => 'created_at', :dependent => :destroy
class Comment < ActiveRecord::Base
belongs_to :graphic, :foreign_key => :asset_id
It seems to me like the has_many should not have the foreign_key (it's referenced in the belongs_to ok I believe) but I am not sure, do you know?
i.e. should it be
class Graphic < ActiveRecord::Base
has_many :comments,
:conditions => 'asset_type_id = 5',
:order => 'created_at', :dependent => :destroy
class Comment < ActiveRecord::Base
belongs_to :graphic, :foreign_key => :asset_id
I think you are trying to do something that is already baked in Rails. You should use Polymorphic Associations here.
class Comment
belongs_to :asset, :polymorphic => true
end
class Graphic
has_many :comments, :as => :assets
end
This way, you need to declare foreign_key on neither side.
In a has_many statement for rails, :foreign_key is indeed an option which has this description in the ActiveRecord documentation:
Specify the foreign key used for the association. By default this is guessed to be the name of this class in lower-case and “_id” suffixed. So a Person class that makes a has_many association will use “person_id” as the default :foreign_key.
So in your case, it looks as though you do need the foreign_key attribute on your has_many statement, since it differs from the name of the class.
However, you shouldn't need the foreign_key declaration in your belongs_to statement. Here is the description for the :foreign_key option for a belongs_to relationship in the ActiveRecord documentation:
Specify the foreign key used for the association. By default this is guessed to be the name of the association with an “_id” suffix. So a class that defines a belongs_to :person association will use “person_id” as the default :foreign_key. Similarly, belongs_to :favorite_person, :class_name => "Person" will use a foreign key of “favorite_person_id”.
I'm assuming that what you really meant to write for your Comment class is this:
class Comment < ActiveRecord::Base
belongs_to :graphic, :foreign_key => :graphic_id
In this case you could simplify the belongs_to statement to simply this:
belongs_to :graphic

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?

For an ActiveRecord one-to-many association, which :foreign_key option should I set?

ActiveRecord's has_many and belongs_to methods both take a :foreign_key option. If I need to use it to handle a nonstandard FK column name, should I set it for the parent model (has_many), child model (belongs_to), or both, or does it matter?
You should set the :foreign_key option on both.
Consider the following two models:
class Article < ActiveRecord::Base
has_many :comments, :foreign_key => "articleID"
end
class Comment < ActiveRecord::Base
belongs_to :article, :foreign_key => "articleID"
end
Declaring has_many in the Article class allows you to do:
Article.find(12).comments # Finds all comments with comment.articleID == 12
The belongs_to method call in the Comment model allows for:
Comment.last.article # Finds article with article.id == comment.articleID
As you can see, in both cases the foreign key is used. If omitted in either location, that particular association proxy will not work properly.
belongs_to guesses the foreign key to be the association name plus _id.
has_one guesses the foreign key to be the name of the containing class plus _id.
Usually for a nonstandard key, you only need it in one place.
class Book < ActiveRecord::Base
# Rails default fk is isbn_id
belongs_to :isbn, :class_name => "BarCode", :foreign_key => "bar_code_id"
end
class BarCode < ActiveRecord::Base
# Rails default fk is bar_code_id, so you don't need to specify it
has_one :book
end

Resources