Quantcast
Viewing all articles
Browse latest Browse all 34

More ActiveRecord Notes

I started reading ActiveRecord::Base a few days ago and found 8 things that I didn’t know about that it offered. I also only made it about 1/4 of the way through the code. Here are a few new things I’ve learned upon further reading:

1. Find by multiple ids

Not only can you find a single record by calling find_by_id, you can find multiple records by providing an array of ids.

User.find_by_id([1, 12, 55])
# Returns 3 User objects with ids of 1, 12, and 55. If any isn't found, then RecordNotFound is raised.

2. Locking database records

If you have multiple processes that may update the same record (like incrementing a counter), then you may run into a problem where they both pull the record when the counter = 42. They each update the counter to 43 and save the record. This results in a deviation from reality of 1.

The solution is to lock the record while updating it. Here’s the code:

User.transaction do
  user = User.find(1, :lock => true)
  user.counter += 1
  user.save!
end

If second process running the above code executes while there is a lock in place, it will block until the first process finishes its transaction, at which point, the lock is lifted. The second can then get the record, now updated, and do its update.

3. Object.allocate

When doing any sort of find on an ActiveRecord model, after the query has been executed, the rows of the results are instantiated into objects by the instantiate method:

(From ActiveRecord 2.3.4)

        def instantiate(record)
          object =
            if subclass_name = record[inheritance_column]
              # No type given.
              if subclass_name.empty?
                allocate

              else
                # Ignore type if no column is present since it was probably
                # pulled in from a sloppy join.
                unless columns_hash.include?(inheritance_column)
                  allocate

                else
                  begin
                    compute_type(subclass_name).allocate
                  rescue NameError
                    raise SubclassNotFound,
                      "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
                      "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
                      "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
                      "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
                  end
                end
              end
            else
              allocate
            end

          object.instance_variable_set("@attributes", record)
          object.instance_variable_set("@attributes_cache", Hash.new)

          if object.respond_to_without_attributes?(:after_find)
            object.send(:callback, :after_find)
          end

          if object.respond_to_without_attributes?(:after_initialize)
            object.send(:callback, :after_initialize)
          end

          object
        end

Unless you’re using Single Table Inheritance, the only thing that is interesting is the allocate on line 1646 and the code following it.

Effectively, from what I can gather, Class#allocate creates the space in memory for the object. The following lines set the attributes for the ActiveRecord object and then trigger the callbacks.

According to this article the purpose of using allocate, rather than new is to avoid calling the ActiveRecord initialize method, which sets things up like ActiveRecord::Base#new_record? returning true when the record is not new.

I’m still learning about this stuff, so if you have anything to add, I’d appreciate it. (I may start reading the C code behind Ruby itself next.)

4. Attribute Level Access Control

I’ve seen this before, but I didn’t realize that there were different approaches to the access level on your attributes. Rails gives you three settings for controlling mass assignment on your Model attributes:

  • attr_protected
  • attr_accessible
  • attr_readonly

They’re pretty self explanatory. If you declare any attribute protected, it opens all other attributes that are not declared protected for mass assignment. I’m sure you can understand the “always open” security setting’s implication.

If you declare an attribute accessible, all other non-accessible attributes are protected from mass assignment. Having been in IT and being security minded, this seems like the idea way to go.

Finally, I’m having trouble thinking of an instance where you might make an attribute readonly. It may make sense to block changes to created_at or created_by attributes, but besides those, prohibiting all updates seems a little excessive in most cases.


Viewing all articles
Browse latest Browse all 34

Trending Articles