Using :include with nested tables through foreign_key relationships - ruby-on-rails

A CertProgramItem has_many :cert_schedules.
A CertSchedule belongs_to :reg_fee_item, :foreign_key => 'reg_fee_item_id', :class_name => 'Item'
Starting with the CertProgramItem, I want to get all CertSchedules and their related tables in one query (to avoid the n+1 problem). My first query was:
cpi_arr = CertProgramItem.find(:all, :include => :cert_schedules, :order => :id)
However, this didn't fetch the members of the Item class which belong to the collection of CertSchedules.
I have modified the query:
cpi_arr = CertProgramItem.find(:all, :include => {:cert_schedules => :items}, :order => :id)
and
cpi_arr = CertProgramItem.find(:all, :include => {:cert_schedules => :reg_fee_items}, :order => :id)
but I get errors like ActiveRecord::ConfigurationError: Association named 'items' was not found; perhaps you misspelled it?" or ActiveRecord::ConfigurationError: Association named 'reg_fee_items' was not found; perhaps you misspelled it? for the 2nd.
Is there a way to get this nested, foreign-key association in one query?

Here's some more detailed information on the CertSchedule assocciations:
class CertSchedule < ActiveRecord::Base
belongs_to :cert_program_item
belongs_to :reg_fee_item, :foreign_key => 'reg_fee_item_id', :class_name => 'Item'
belongs_to :start_term, :class_name => 'SchoolTerm', :foreign_key => 'start_term_id'
My latest version of the query looks like this:
cpi_arr = CertProgramItem.find(:all, :include => [:cert_tier, {:cert_schedules => [:reg_fee_item,:start_term] }])
This query is now successfully returning what I expected. Lessons learned:
Use the foreign key name from the model, not the actual table name.
Multiple items in an association need to be surrounded with square brackets [].

Related

Rails 4.0.0 foreign key for models

Is this wrong to have that same foreign key for two different models? It's Rails 4.0.0 app so conditions are written like that. I ask because I got some problem with blinks and can't find it.
has_many :messages, :conditions => {:deleted => false, :subject_h => ''}
has_many :messages_send, :class_name => "Message", :foreign_key => "sender_id", :conditions => ['deleted_sender = ?', false]
has_many :blinks, :conditions => {:deleted => false, :subject_h => ''}
has_many :blinks_send, :class_name => "Blink", :foreign_key => "sender_id", :conditions => ['deleted_sender = ?', false]
I would do one of the two following things:
1 - Have one table per model (so one blinks table and one messages table).
2 - Use Single Table Inheritance (STI) for each messages. You would have only one table (senders in your case), with a type field that would differentiate between a blink and a message.
For simplicity here, I would personally go for the first option, and evolve to the STI later on if still needed.
The advantage would also be that you'll be able to declare your associations like this:
has_many :messages
has_many :blinks

Rails has_many with source and conditions doesn't create condition attributes

I'm trying to setup a has_many with conditions which works fine for the reading part but not for new entries. I've tested it some weeks ago in a sandbox and it worked but I can't get it work again so maybe I'm just blind or it is just a wrong design :-)
class Task
has_many :task_users
has_many :assignees, :through => :task_users, :source => :user, :conditions => {"task_users.is_assignee" => true}
has_many :participants, :through => :task_users, :source => :user
end
class TaskUser < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class User
has_many :tasks
end
After adding a new assignee to a task like this
Task.first.assignees << User.first
the following SQL is executed
SQL (0.3ms) INSERT INTO `task_users` (`created_at`, `is_assignee`, `task_id`, `updated_at`, `user_id`) VALUES ('2012-11-18 15:52:24', NULL, 2, '2012-11-18 15:52:24', 3)
I thought rails will use my conditions to set these values when I'm add ing new ones. Reading works great but I have no idea why adding new values doesn't work with conditions.
I expect this INSERT
SQL (0.3ms) INSERT INTO `task_users` (`created_at`, `is_assignee`, `task_id`, `updated_at`, `user_id`) VALUES ('2012-11-18 15:52:24', 1, 2, '2012-11-18 15:52:24', 3)
I'm not entirely sure whether you can specify :conditions hash on the join table in a has_many :through association. Someone else correct me if I'm wrong, but the condition has to be directly on the source association, :user in your case.
If this is the case, to work around this you can specify an auxiliary association:
has_many :task_users
has_many :assignee_task_users, :class_name => 'TaskUser', :conditions => {"is_assignee" => true}
has_many :assignees, :through => :assignee_task_users, :source => :user
just going to highlight the documentation:
Specify the conditions that the associated objects must meet in order to be
included as a WHERE SQL fragment, such as price > 5 AND name LIKE 'B%'.
Record creations from the association are scoped if a hash is used.
has_many :posts, :conditions => {:published => true} will create published
posts with #blog.posts.create or #blog.posts.build.
even though you used an hash already, the first parameter is a string, which is unneded to be (the association knows already the table name). rewrite it as :conditions => {:is_assignee => true} and it should work.
Also, the way you are creating users should be rewritten in order for this to work of course. Instead of:
Task.first.assignees << User.first
use:
Task.first.assignees.create
and that should do the trick.

Why can't records with piggy-back attributes be saved?

I recently run into a problem where records were marked as readonly. Checking out the documentation I found this:
"Records loaded through joins with piggy-back attributes will be marked as read only since they cannot be saved. "
Why not? My model looks like the following:
class MailAccount
belongs_to :account, :class_name => "UserAccount"
named_scope :active, :joins => :account,
:conditions => "user_accounts.archived_at IS NULL"
end
I find no reason why models loaded retrieved with this named scope can not be saved. Any ideas?
It turned out I had to add :select => "mail_accounts.*" to the scope, or otherwise the query would store attributes from user_accounts in the MailAccount object, which prevented it from being saved.
So the proper code to use is:
class MailAccount
belongs_to :account, :class_name => "UserAccount"
named_scope :active, :joins => :account,
:conditions => "user_accounts.archived_at IS NULL",
:select => "mail_accounts.*"
end
When you use a :join, the ActiveRecord model for that associated object is not instantiated. You should use :include instead.

How do I order by foreign attribute for belongs_to reference where there are 2 keys to foreign table

I have a Model which has a belongs_to association with another Model as follows
class Article
belongs_to :author, :class_name => "User"
end
If I wanted to find all articles for a particular genre ordered by author I would do something like the following
articles = Article.all(:includes => [:author], :order => "users.name")
However if Article happens to have two references to User how can I sort on :author?
class Article
belongs_to :editor, :class_name => "User"
belongs_to :author, :class_name => "User"
end
I have tried
articles = Article.all(:includes => [:author], :order => "users.name")
#=> incorrect results
articles = Article.all(:includes => [:author], :order => "authors.name")
#=> Exception Thrown
My first attempted solution
A half solution is as follows. It was not totally obvious but looking at my logs I figured it out.
class Article
belongs_to :editor, :class_name => "User"
belongs_to :author, :class_name => "User"
end
Basically you need to do the following
articles = Article.all(:include => [:editor,:author], :order => 'articles_authors.name')
articles = Article.all(:include => [:editor,:author], :order => 'authors_articles.name')
It is the naming of the alias that I missed (articles_authors)
The issue with this is that the following does not work although it seems like it should.
articles = Article.all(:include => [:editor,:author], :order => 'authors_articles.name')
articles = Article.all(:include => [:editor,:author], :order => 'editors_articles.name')
This may be an issue if you have a UI table and want to send the order field to the controller. So you may want to first order on author then editor. But it would fail for for one of the queries (unless you dynamically change the include too)
Update - Added this to the original Question.
So I think I have nailed it. It was not totally obvious but looking at my logs I figured it out.
class Article
belongs_to :editor, :class_name => "User"
belongs_to :author, :class_name => "User"
end
Basically you need to do the following
articles = Article.all(:include => [:editor,:author], :order => 'articles_authors.name')
articles = Article.all(:include => [:editor,:author], :order => 'authors_articles.name')
It is the naming of the alias that I missed (articles_authors)
This is called Table Aliasing in ActiveRecord. When the find method joins the same table more than once the alias names for the table is determined as follows:
Active Record uses table aliasing in the case that a table is referenced
multiple times in a join. If a table is referenced only once, the standard table
name is used. The second time, the table is aliased as
#{reflection_name}_#{parent_table_name}. Indexes are appended for any more
successive uses of the table name.
Refer to the ActiveRecord documentation for more details. Search for Table Aliasing to navigate to the specific section.

Activerecord Nested :include fails

I have an AR query using 'will_paginate' that looks like this:
paginate :all,
:page => criteria[:page],
:per_page => criteria[:per_page],
:include => { :user, :person },
:conditions => [conditions , criteria[:from_date], criteria[:to_date], criteria[:patient_id],criteria[:user_id]].concat(criteria[:actions]).concat(criteria[:types]).concat(criteria[:users]).concat(criteria[:statuses]).concat(criteria[:priorities]).compact,
:order => criteria[:order]
I get an error in the order clause:
Unknown column 'user.person.last_name' in 'order clause'
I am trying to order by a person's last name. As you can see I have included user and person in a nested include. User belongs to person with this statement:
belongs_to :person, :class_name => 'Party', :foreign_key => 'person_id', :with_disabled => true
Person is a subclass of Party:
class Person < Party
Party has a last_name field
The order by should be table_name.column, something like people.last_name

Resources