Rails Issue with associations using primary_key+foreign_key options - ruby-on-rails

I want to access a legacy database schema from Rails. I have one table NAGIOS_OBJECTS with a primary key OBJECT_ID and one table NAGIOS_HOST_CHECKS that refers to NAGIOS_OBJECTS with a column HOST_OBJECT_ID. I thus defined the relations as follows:
class NagiosObject < ActiveRecord::Base
has_one :nagios_host_check, :foreign_key => :host_object_id, :primary_key => :object_id
end
class NagiosHostCheck < ActiveRecord::Base
belongs_to :nagios_object, :foreign_key => :host_object_id, :primary_key => :object_id
end
However, when calling a_nagios_object.nagios_host_check or a_nagios_host_check.nagios_object, I always get nil.
Any idea what is wrong with my code?

foreign_key and primary_key should be strings, not symbols
ex:
class NagiosObject < ActiveRecord::Base
has_one :nagios_host_check, :foreign_key => 'host_object_id', :primary_key => 'object_id'
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001317

Related

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

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?

Ruby on rails - Reference the same model twice?

Is it possible to set up a double relationship in activerecord models via the generate scaffold command?
For example, if I had a User model and a PrivateMessage model, the private_messages table would need to keep track of both the sender and recipient.
Obviously, for a single relationship I would just do this:
ruby script/generate scaffold pm title:string content:string user:references
Is there a similar way to set up two relations?
Also, is there anyway to set up aliases for the relations?
So rather than saying:
#message.user
You can use something like:
#message.sender or #message.recipient
Any advice would be greatly appreciated.
Thanks.
Here's a complete answer to this issue, in case people visiting this question are new to Ruby on Rails and having a hard time putting everything together (as I was when I first looked into this).
Some parts of the solution take place in your Migrations and some in your Models:
Migrations
class CreatePrivateMessages < ActiveRecord::Migration
def change
create_table :private_messages do |t|
t.references :sender
t.references :recipient
end
# Rails 5+ only: add foreign keys
add_foreign_key :private_messages, :users, column: :sender_id, primary_key: :id
add_foreign_key :private_messages, :users, column: :recipient_id, primary_key: :id
end
end
Here you are specifying that there are two columns in this table that will be referred to as :sender and :recipient and which hold references to another table. Rails will actually create columns called 'sender_id' and 'recipient_id' for you. In our case they will each reference rows in the Users table, but we specify that in the models, not in the migrations.
Models
class PrivateMessage < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
Here you are creating a property on the PrivateMessage model named :sender, then specifying that this property is related to the User class. Rails, seeing the "belongs_to :sender", will look for a column in your database called "sender_id", which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the recipient.
This will allow you to access your Sender and Recipient, both instances of the User model, through an instance of the PrivateMessage model, like this:
#private_message.sender.name
#private_message.recipient.email
Here is your User Model:
class User < ActiveRecord::Base
has_many :sent_private_messages, :class_name => 'PrivateMessage', :foreign_key => 'sender_id'
has_many :received_private_messages, :class_name => 'PrivateMessage', :foreign_key => 'recipient_id'
end
Here you are creating a property on the User Model named :sent_private_messages, specifying that this property is related to the PrivateMessage Model, and that the foreign key on the PrivateMessage model which relates it to this property is called 'sender_id'. Then you are doing the same thing for received private messages.
This allows you to get all of a users sent or received private messages by doing something like this:
#user.sent_private_messages
#user.received_private_messages
Doing either of these will return an array of instances of the PrivateMessage model.
....
Add this to your Model
belongs_to :sender, :class_name => "User"
belongs_to :recipient, :class_name => "User"
And you are able to call #message.sender and #message.recipient and both reference to the User model.
Instead of user:references in your generate command, you'd need sender:references and recipient:references
hi there
to have both side relation do as bellow in your both models:
class Message < ActiveRecord::Base
belongs_to :sender,
:class_name => "User",
:foreign_key => "sender_id"
belongs_to :recipient,
:class_name => "User",
:foreign_key => "recipient_id"
end
class User < ActiveRecord::Base
has_many :sent,
:class_name => "Message",
:foreign_key => "sent_id"
has_many :received,
:class_name => "Message",
:foreign_key => "received_id"
end
I hope this help you...
The above answers, while excellent, do not create foreign key constraints in the database, instead only creating indexes and bigint columns. To ensure that the foreign key constraint is enforced, add the following to your migration:
class CreatePrivateMessages < ActiveRecord::Migration[5.1]
def change
create_table :private_messages do |t|
t.references :sender
t.references :recipient
end
add_foreign_key :private_messages, :users, column: :sender_id, primary_key: :id
add_foreign_key :private_messages, :users, column: :recipient_id, primary_key: :id
end
end
This will ensure that the indices get created on the sender_id and recipient_id as well as the foreign key constraints in the database you're using.

Thinking Sphinx - RuntimeError: Missing Attribute for Foreign Key

Trying to get along with Sphinx/Thinking Sphinx for the first time.
I've got my models defined as follows (simplified):
class Branch < ActiveRecord::Base
has_many :salesmen, :class_name => "User"
has_many :leads, :through => :salesmen
end
class User < ActiveRecord::Base
belongs_to :branch
has_many :leads, :foreign_key => "owner_id"
end
class Lead < ActiveRecord::Base
belongs_to :owner, :class_name => "User"
define_index do
indexes company_name
indexes :name, :sortable => true
has owner.branch_id, :as => :branch_id
indexes [owner.last_name, owner.first_name], :as => :owner_full_name, :sortable => true
end
end
Anytime I call
Branch.first.leads.search
I get
RuntimeError: Missing Attribute for Foreign Key branch_id
What am I doing wrong?
The issue is that Thinking Sphinx needs branch_id as an attribute in your index, so it can restrict results to just the relevant branch (because you're searching within an association).
It's not clear from your associations (or maybe that just my dire need for sleep) whether a lead belongs to a branch via the owner, or directly as well. If the former, Ben's suggestion is probably correct. Otherwise, try adding the following to your define_index block:
has branch_id, :as => :direct_branch_id
An alternative approach, after reading the comments, is to add your own search method to the leads association in Branch. A vague attempt (you will need to debug, I'm sure):
has_many :leads, :through => :salesmen do
def search(*args)
options = args.extract_options!
options[:with] ||= {}
options[:with][:branch_id] = proxy_owner.id
args << options
Lead.search(*args)
end
end
This should get around the fact that you do not have a direct reference to the branch from Lead. The only possible issue is that I'm not sure whether custom extensions get loaded before or after what Thinking Sphinx injects. Give it a shot, see if it helps.
If you say a Lead belongs_to a Branch, then you must have a branch_id in the leads table. Since you don't, it's not a belongs_to relationship. I think you need something like this:
class Branch < ActiveRecord::Base
has_many :leads, :through => :salesmen
has_many :salesmen, :class_name => "User"
end
class User < ActiveRecord::Base
belongs_to :branch # users table has branch_id
has_many :leads, :foreign_key => "owner_id"
end
class Lead < ActiveRecord::Base
belongs_to :owner, :class_name => "User" # leads table has owner_id
define_index do
indexes :company_name
indexes :name, :sortable => true
has owner.branch_id, :as => :branch_id
indexes [owner.last_name, owner.first_name], :as => :owner_full_name, :sortable => true
end
end
I believe you're missing a :through option on your branch relation. Try updating to:
class Lead < ActiveRecord::Base
has_one :branch, :through => :owner

Resources