Archive for the ‘activerecord’ Category

When Empty Isn’t Empty With Rails Active Record Collections

Sunday, November 4th, 2007

So I’m writing some Rails code, just playing around, and I run into a very strange situation. I’ve found a situation where Rails’ Active Record code is calculating the results to empty? incorrectly, even if I add elements to the collection. So I had a has_many collection with elements inside of it, but calls to length or empty were returning 0 and true.

Here’s my simple models:


class Query < ActiveRecord::Base
  has_many :measures
  validates_presence_of :cube_name

  def measure_attributes=(attrs)
    attrs.each {|a| measures.build(a)}
  end
end

and


class Measure < ActiveRecord::Base
  belongs_to :query
  acts_as_list :scope => :query
  has_one :condition, :as => :conditionable
end

Notice that in Query, I’ve added a measure_attributes method which automatically handles the construction of new Measure instances from the form parameters.

Here’s the simple nested form. From the models, a Query has many Measures:



<% form_for :query, :url => {:action => :new} do |f| %>
	Cube Name: <%= f.text_field :cube_name %>

	<% @query.measures.each do |measure| %>
		<div>
		<% fields_for "query[measure_attributes][]", measure do |mf| %>
			Measure Name: <%= mf.text_field :name %>
		<% end %>
		</div>
	<% end %>

	<%= submit_tag %>
<% end %>

Here’s what I was doing in my controller. Again, very simple:


class QueriesController < ApplicationController
  def new
    @query = Query.new(params[:query])
    @query.measures.build if @query.measures.empty?
  end
end

Now, I wouldn’t normally have the new method handle it this way (I would have both a create and a new), but this was test code. My check against empty? was basically saying “if this is the first time I’ve hit this method, throw in a blank Measure so the form will show at least show one Measure.”

Well, it turns out, that call to empty? is calculated by checking the database! Here’s the actual query:


SELECT count(*) AS count_all FROM measures WHERE (measures.query_id = NULL)

Even though I added Measures via measure_attributes, ActiveRecord here doesn’t seem to acknowledge that at this point.

When I rendered the new template after the form is submitted, the loop in the RHTML (@query.measures.each) doesn’t iterate, because it thinks there’s no measures.

Sigh.

So, here’s what we learned. Don’t call length or empty? on an Active Record managed has_many collection unless you understand how it’s calculating the size of the contents (via the database). (Also, I still don’t quite understand when it goes to the database and when it uses the actual items I’ve manually added.)

Here’s the best way to handle this situation, completely avoiding the call to empty?:


class QueriesController < ApplicationController
  def new
    @query = Query.new(params[:query])
    # add one so the form will have something to render in the beginning
    @query.measures.build
  end

  def create
    @query = Query.new(params[:query])
    render :action => ‘new’
  end
end

Encapsulate Relationships in Rails

Tuesday, August 8th, 2006

It would be really nice if I could encapsulate all of the default accessors that are generated when I create a relationship between two ActiveRecord objects.

For instance, when you declare that `has_and_belongs_to_many :people`, your model is peppered with public accessors for the people collection. These methods are generated for your class at runtime.

I want to provide business logic around those people, and therefore I want to prohibit clients from directly accessing the people collection. I want to provide methods such as `register_person`. Of course, I can add that method, but there’s nothing stopping someone from coming along and calling `model.people << person`.

Is there? How to restrict direct access to the collection on an ActiveRecord model?

Add MetaData to your Models in Rails

Monday, February 27th, 2006

Add metadata about your tables to your models in Rails.

>Rails model files contain no information on the tables they represent. This is a good thing in general, because it reduces duplication—add a column to a table, and there’s no configuration to update in the model.

>However, when you’re writing code, it’s sometimes nice to be able to see just what attributes a model has.

>Enter annotate models, a really trivial Rails plugin I hacked up in the plane back from the first No Fluff of the year. The plugin adds a comment block to the top of each model file, documenting the schema. If you update the schema, run it again and it updates the comment.

Understanding Migrations in Ruby on Rails

Friday, January 13th, 2006

UnderstandingMigrations in Ruby on Rails

> ActiveRecordMigration allow you to use Ruby to define changes to your database schema, making it possible to use a version control system to keep things synchronised with the actual code.

Rails and Allowing ID Editing from Forms

Sunday, December 4th, 2005

Need to allow yours users to directly edit the ID of your model object?

By default, Rails will strip out the ID field from the hash of request parameters. This is a security measure, prohibitings the web interface from altering an object’s ID.

Generally, this is a good practice. However, some legacy schemas have IDs that are not simply numbers. In those cases, the ID is created and managed by the user.

To tell Rails to allow the ID to be edited, you can use attr_protected and attr_allowed.

More information is available at http://wiki.rubyonrails.com/rails/pages/HowToEnsureValidAttributesInFormData

Rails, Legacy Schemas, ActiveRecord, and has_and_belongs_to_many

Friday, December 2nd, 2005

There turns out to be an issue with legacy schemas and ActiveRecord, the ORM layer of Rails when using has_and_belongs_to_many.

Note: This issue might be Oracle specific.

If the join table itself has an id column, and it has the same name as the id column from the association table, then it will override the id from the association table.

This means that instances from the association table will have the wrong id.

The solution looks to be the new :finder_sql parameter of the has_and_belongs_to_many relationship. With :finder_sql, you can override the generated SQL.

How to use :finder_sql? Try:

has_and_belongs_to_many :ksaas, :join_table => "tbl_ksaa_objective",
  :foreign_key => "objective_cid", :association_foreign_key => "ksaa_cid",
  :finder_sql => "SELECT TBL_KSAA.cid, TBL_KSAA.ksaa_type, TBL_KSAA.ksaa_item, " +
    "TBL_KSAA.KSAA_TYPEID, TBL_KSAA.SAMPLE_BEHAVIOR " +
    "FROM TBL_KSAA LEFT JOIN TBL_KSAA_OBJECTIVE ON " +
    "TBL_KSAA.cid = TBL_KSAA_OBJECTIVE.ksaa_cid WHERE " +
    '(TBL_KSAA_OBJECTIVE.objective_cid = #{id} )'

Keep those single quotes in order to lazily interpret the id in the query.

Note that Hibernate does not have this problem, as it aliases all column names, even when not using joins. Therefore it doesn’t have the column naming conflicts.

Using Columns with Reserved Names in Rails

Thursday, December 1st, 2005

From Rails Mailing List : How to access column with a reserved name?

def new_column
  read_attribute 'old_column'
end

def new_column=(value)
  write_attribute 'old_column', value
end