Complex joins with legacy tables in Rails - ruby-on-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?

Related

Rails 4 oracle enhanced adapter many to many issue

First things first
Using:
rails4
oracle enhanced adapter rails4 branch
I have a many to many relationship mapped on an existing database.
My models look as such:
class EventMap < ActiveRecord::Base
self.table_name="TAKE_PART"
self.primary_key="id"
belongs_to :event, foreign_key: "lottery_event_id"
belongs_to :entrant, foreign_key: "address_id"
end
class Event < ActiveRecord::Base
self.table_name="THE_EVENT"
self.primary_key="id"
has_many :event_maps
has_many :entrants, :through => :event_maps
end
class Entrant < ActiveRecord::Base
self.table_name="ADDRESSES"
self.primary_key="id"
self.set_date_columns :date_of_birth
has_many :event_maps
has_many :events, :through => :event_maps
end
When I try to obtain all addresses for an event:
def show
#entrants = Event.find(params[:id]).entrants
end
I get an Oracle error as such:
OCIError: ORA-00904: "TAKE_PART"."ENTRANT_ID": SELECT "THE_EVENT".* FROM "THE_EVENT" INNER JOIN "TAKE_PART" ON "THE_EVENT"."ID" = "TAKE_PART"."LOTTERY_EVENT_ID" WHERE "TAKE_PART"."ENTRANT_ID" = :a1
The issue here seems to be that the foreign key of TAKE_PART is not properly used, it should be address_id, instead it uses the model name entrant_id
Is this a bug in oracle enhanced or am I doing something wrong in the matching of the tables?
Associations of your classes are not completely defined, bc both Event and Entrant don't know proper ids, they should pass to EventMap. Proper way is following:
class EventMap < ActiveRecord::Base
...
belongs_to :event, :foreign_key => "lottery_event_id"
belongs_to :entrant, :foreign_key => "address_id"
end
class Event < ActiveRecord::Base
...
has_many :event_maps, :foreign_key => "lottery_event_id"
has_many :entrants, :through => :event_maps
end
class Entrant < ActiveRecord::Base
...
has_many :event_maps, :foreign_key => "address_id"
has_many :events, :through => :event_maps
end
It works because event.entrants implies following actions for database (simplified):
Join two tables EventMap and Entrant, so that address_id <=> entrant.id (Mapping 1)
In joined table find all strings with lottery_event_id = event.id (Mapping 2)
Mapping 1:
has_many :entrants, :through => :event_maps #Join two tables
belongs_to :entrant, :foreign_key => "address_id" #matching address_id with entrants.id
#belongs_to+foreign_key tells to EventMap which key to use for referring to Event object
Mapping 2:
has_many :event_maps, :foreign_key => "lottery_event_id" #associate event.id with lottery_event_id
#has_many+foreign_key tells to Event which key EventMap uses for referring to Event object
Or in more formal way:
SELECT "entrants".* FROM "entrants" INNER JOIN "event_maps" ON "entrants"."id" =
"event_maps"."address_id" WHERE "event_maps"."lottery_event_id" = ? [["lottery_event_id", 1]]

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 3 / Active Record - Association problems with additional model in assignment table

I currently have a setup that links the models User, Dealer and Role together. User and Dealer is many to many, and is working as expected with a Dealer_user assignment table.
The problem is that I want to have roles assigned to the user that are specific to the dealer also (i.e. a user could be a Sales Manager and a Parts Manager in one dealership, while being a Sales Manager and a Director in another).
In order to do this, I have a Role model (which belongs to a Role_type). Role should belong to Dealer_user, and Dealer_user has many Roles.
The intention is that I will be able to do dealer.users.where(:id => user.id).first.roles and it will return only the roles specific to that dealership.
The problem I have is that when I run the following test code: dealer.users.where(:id => user.id).first.roles.create(:role_type_id => 1 + Random.rand(4))
I get an error: Cannot modify association 'User#roles' because the source reflection class 'Role' is associated to 'DealerUser' via :has_many.
Can anyone suggest what I am doing wrong with my models (which are below)?
NOTE: The belongs_to relationship that Role has with Dealer_user is polymorphic because it could also belong to Sale_user or other association tables, which require the same functionality as Dealer.
class Dealer < ActiveRecord::Base
attr_accessible :name, :address_id
has_many :dealer_users
has_many :users, :through => :dealer_users
has_many :roles, :through => :dealer_users
end
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :dealer_users
has_many :dealers, :through => :dealer_users
has_many :roles, :through => :dealer_users
end
class DealerUser < ActiveRecord::Base
attr_accessible :dealer_id, :user_id
belongs_to :dealer
belongs_to :user
has_many :roles, :as => :role_originator
end
class Role < ActiveRecord::Base
attr_accessible :role_type_id
belongs_to :role_type
belongs_to :role_originator, :polymorphic => true
end
Edit: No luck so far - can anyone help?
I would use has_many through association with the roles table. Get rid of the dealer_user table, and add columns to the roles table dealer_id and user_id
Then your models would look something like this:
class Dealer < ActiveRecord::Base
has_many :users, :through => :roles
has_many :roles
end
class User < ActiveRecord::Base
has_many :dealers, :through => :roles
has_many :roles
end
class Role < ActiveRecord::Base
belongs_to :dealer
belongs_to :user
end
That should make it easier to do the types of queries you're trying. The rails guide has a really good overview here

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

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

Resources