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]]
Related
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
I've seen this issue referenced a few times, but nothing too complete. I'm having a problem with using a join table for a single model. For example, suppose we have Users and Highfives. Highfives will just be a join table for the two users highfiving. So I have this:
class Highfive < ActiveRecord::Base
belongs_to :user1,
:class_name => "User"
belongs_to :user2,
:class_name => "User"
end
class User < ActiveRecord::Base
has_many :highfives
end
However, with this, I am unable to do something like User.find(1).highfives since that generates a query like:
SELECT "highfives".* FROM "highfives" WHERE "highfives"."user_id" = 1
Really, I should be getting a query like:
SELECT "highfives".* FROM "highfives" WHERE "highfives"."user1_id" = 1 or "highfives"."user2_id" = 1
I imagine to do this I'll need to modify my User model in some way. But what am I missing?
Thanks.
You need to specify the foreign key in your has_many statement, otherwise Rails will assume it's user_id:
class User < ActiveRecord::Base
has_many :highfives, :foreign_key => :user1_id
end
Of course, this only works for a single foreign key. In your case, you'd probably want an instance method instead:
class User < ActiveRecord::Base
def highfives
Highfive.where("user1_id = ? or user2_id = ?", id, id)
end
end
Or, assuming it's impossible for a User to highfive himself:
class User < ActiveRecord::Base
has_many :highfives1, :class => "Highfive", :foreign_key => :user1_id
has_many :highfives2, :class => "Highfive", :foreign_key => :user2_id
def highfives
highfives1 + highfives2
end
end
Specify :foreign_key in your Models. So..
class Highfive < ActiveRecord::Base
belongs_to :user1,
:class_name => "User",
:foreign_key => "user1_id"
belongs_to :user2,
:class_name => "User",
:foreign_key => "user2_id"
end
class User < ActiveRecord::Base
has_many :highfive1,
:class_name => "Highfive",
:foreign_key => "highfive1_id"
has_many :highfive2,
:class_name => "Highfive",
:foreign_key => "highfive2_id"
end
Reference!
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
I've got a Project model, and a Contact model. The Project model has an owner and a client, both of which are Contacts. I've obviously got something ambiguous going on, because if I have a contact and ask for its projects, Rails won't know whether I'm asking for it's projects where it's the client or where it's the owner. So far I've got this:
class Contact < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :owner, :class_name => 'Contact', :foreign_key => 'owner_id'
belongs_to :client, :class_name => 'Contact', :foreign_key => 'client_id'
end
How do I make two relationships here?
Its similar to the way belongs_to is defined in the other class.
So Basically
class Contact < ActiveRecord::Base
has_many :projects_owned, :class_name => "Project", :foreign_key => "owner_id"
has_many :projects_as_client, :class_name => "Project", :foreign_key => "client_id"
end
names of associations could be better. The Single Table inheritance approach described before me is also a neat way, but go for it if you have a lot of different behaviour for each of the OwnerContact and ClientContact class, otherwise it might be just a useless overhead.
I think here's should be polymorphic association, something like this
class Owner < ActiveRecord::Base
has_many :projects, :as => :person
end
class Client < ActiveRecord::Base
has_many :projects, :as => :person
end
class Project < ActiveRecord::Base
belongs_to :person, :polymorphic => true
end
Now you can retrieve projects by #client.projects or #owner.projects. If you want to get person from #project you should add to Project migration this:
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.references :person, :polymorphic => true
t.timestamps
end
end
...
You should try to use a single table inheritance on the Contact table. All you need to do for this to work is to implement a 'type' column (string). Rails will handle the rest
class Contact < ActiveRecord::Base
# implement a type column
has_many :projects
end
class OwnerContact < Contact
end
class ClientContact < Contact
end
class Project < ActiveRecord::Base
belongs_to :owner, :class_name => 'OwnerContact'
belongs_to :client, :class_name => 'ClientContact'
end
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?