Workaround for has_many :through overriding the :select clause
As I mentioned earlier a named_scope select clause will get overridden in a has_many :through.
class Grandparent has_many :grandchildren, :through => :children end class Grandchild named_scope :summed_ages_by_gender, :select => "SUM(age) as age", :group => :gender end Grandparent.first.grandchildren.summed_ages_by_gender #does not do what you want, because the through overrides the select
If you have a case where this isn’t acceptable, such when you have a complicated aggregate select that you would like to call on a has_many through, there is an ugly work-around. You can recreate the has_many :through, with joins in a scope on the child model, then return that in a method of the grandparent.
class Grandparent
def grandchildren
Grandchild.grandchildren.scoped({:conditions => ['grandparents.id = ?'], id})
end
end
class Grandchildren
named_scope :summed_ages_by_gender, :select => "SUM(age) as age", :group => :gender
named_scope :grandchildren, :joins => ['JOIN parents ON grandchildren.parent_id => parents.id', 'JOIN grandparents ON parents.grandparents_id = grandparents.id']
end
Grandparent.first.grandchildren.summed_ages_by_gender
#should work
Obviously I would not advise using this as a relationship model – it was one I just threw together for elucidation. And I haven’t run this exact code, but I have run the code it is a dummied up copy of, with great success.
Maybe I’ll try and come up with a patch to fix the original problem, but the through select logic is probably some of the most edge-case ridden in AR associations, so I am not sure how easy it would be.
Update: I made a ticket with a patch for the failing test, at least.
Data massaging in migrations and errors after refactoring 4
ActiveRecord migrations are a powerful tool for handling your database schema changes. Not only that, but you can run any AR code in them to tweak that data itself while migrating to the new structure, such as.
class AddUpdatedAtToReport < ActiveRecord::Migration
def self.up
add_column :fishing_reports, :updated_at, :datetime
FishingReport.find(:all).each do |r|
r.updated_at = Time.now
r.save
end
end
def self.down
remove_column :fishing_reports, :updated_at
end
end
Unfortunately this can lead to the migration becoming not only obselete but outright incompatible later, throwing errors. For instance, if you later refactor that model out of existence, the migration will fail, because there is no model to match FishingReport. I suspect there could be some future proofing done, by checking for the existence of the class, but one can easily imagine refactorings that wouldn’t be so easily guarded against.
I suppose that could be an excuse for more testing: migration testing. In fact, after using typo for a while, i think migration testing should maybe be a recommended paractice for any publically distributed Rails app.
has_many :through and SimplyHelpful form_for 1
So given how much SimplyHelpful could simplify things by allowing me to have one shared form for both new and edit, all magically handled with a simple "form_for @my_model do |f| ",of course, I jumped at it.
Equally as “of course,” the most complicated piece of the Lost River site that I have been working on is not a simple, single model form, but some variation of a many to many relationship. In fact, it was something best modeled by a “has_many :through” and a case where I definitely wanted the join models easily editable from the creation form.
Now shocking as it may be, how to do this cleanly with the form_for wasn’t immediately apparent to me. In fact, even the reversely eponymous has_many :through blog was slightly misleading (or incorrect?) on this, going through some, of what seem to me to be, unneccessary work-arounds and stating:
has_many :through won’t work with new records, as it needs saved records with actual ids to use in the foreign keys in the join model.
Thankfully I found this post on Rails forum and was able to adapt the collection.build and field_for techniques to my simple_helpful form)for
Report has_many Locations through Conditions and vice versa.
# GET /reports/new
def new
@page_title = 'Creating Report'
@report = Report.new()
#Modify the prepoluation to suit your needs
Location.find(:all).each do |location|
@report.conditions.build(:location => location)
end
render :action => 'edit'
end
I am using Markaby not erb for my templates.
#edit.mab (used for new and edit action)
error_messages_for ‘report’
form_for(@report) do |form|
label ‘Week of’, :for => ‘report_week_of’
text form.text_field(:week_of)
ul{
@report.conditions.each_with_index do |condition, index|
fields_for “conditions[#{index}]”, condition do |f|
li {
text f.hidden_field(:location_id)
h condition.location.name
br
text f.text_area(:text)
}
end
end
}
text form.submit(‘Save »’)
end
# POST /reports
# POST /reports.xml
def create
@page_title = 'Creating Report'
@report = Report.new(params[:report])
params[:conditions].each_value { |condition| @report.conditions.build(condition)} unless params[:conditions].nil?
...
And wallah, a neat little form_for, with a has_many :through. Of course, the general technique can be modified for other similar results. If anyone knows a way to clean-up or simplify further, please let me know.
A reminder if you do have advice to post: comment moderation is on, and so are AJAX only comments, so there will be no immediate feedback if you comment. I’ll work on throwing something into the template to let you know it’s been successfully submitted one of these days.