From: David Heinemeier Hansson Date: 2004-07-23T20:15:55+09:00 Subject: [ANN] Active Record 0.9.0: Thread safety, speed, naturalness What's new in Active Record 0.9.0? ================================== It�s been a while, but the wait will be worth it. This massive update includes thread safety, 400% speed increase (on a 100 objects loop), better callback definitions, natural association assignment, hashes and arrays in YAML storage, and much, much more. Download from http://activerecord.rubyonrails.org, talk on #rubyonrails (FreeNet). * Active Record is now thread safe! (So you can use it with Cerise and WEBrick applications) [Implementation idea by Michael Neumann, debugging assistance by Jamis Buck] * Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute. This brings the tax for using Active Record instead of "riding on the metal" (using MySQL-ruby C-driver directly) down to ~50%. Done by doing lazy type conversions and caching column information on the class-level. * Added callback objects and procs as options for implementing the target for callback macros. * Added "counter_cache" option to belongs_to that automates the usage of increment_counter and decrement_counter. Consider: class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :post end Iterating over 100 posts like this: <% for post in @posts %> <%= post.title %> has <%= post.comments_count %> comments <% end %> Will generate 100 SQL count queries -- one for each call to post.comments_count. If you instead add a "comments_count" int column to the posts table and rewrite the comments association macro with: class Comment < ActiveRecord::Base belongs_to :post, :counter_cache => true end Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life with the object it's specified to belong with and is destroyed like that as well. Typically objects where you would also specify :dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another), you'll have to manage the counter yourself. * Added natural object-style assignment for has_one and belongs_to associations. Consider the following model: class Project < ActiveRecord::Base has_one :manager end class Manager < ActiveRecord::Base belongs_to :project end Earlier, assignments would work like following regardless of which way the assignment told the best story: active_record.manager_id = david.id Now you can do it either from the belonging side: david.project = active_record ...or from the having side: active_record.manager = david If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the project_id attribute on david would be set to the id of active_record, then david would be saved. * Added natural object-style assignment for has_many associations [Florian Weber]. Consider the following model: class Project < ActiveRecord::Base has_many :milestones end class Milestone < ActiveRecord::Base belongs_to :project end Earlier, assignments would work like following regardless of which way the assignment told the best story: deadline.project_id = active_record.id Now you can do it either from the belonging side: deadline.project = active_record ...or from the having side: active_record.milestones << deadline The milestone is automatically saved with the new foreign key. * API CHANGE: Attributes for text (or blob or similar) columns will now have unknown classes stored using YAML instead of using to_s. (Known classes that won't be yamelized are: String, NilClass, TrueClass, FalseClass, Fixnum, Date, and Time). Likewise, data pulled out of text-based attributes will be attempted converged using Yaml if they have the "--- " header. This was primarily done to be enable the storage of hashes and arrays without wrapping them in aggregations, so now you can do: user = User.find(1) user.preferences = { "background" => "black", "display" => large } user.save User.find(1).preferences # => { "background"=>"black", "display"=>large } Please note that this method should only be used when you don't care about representing the object in proper columns in the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through aggregations than this new option. * POSSIBLE CODE BREAKAGE: As a consequence of the lazy type conversions, it's a bad idea to reference the @attributes hash directly (it always was, but now it's paramount that you don't). If you do, you won't get the type conversion. So to implement new accessors for existing attributes, use read_attribute(attr_name) and write_attribute(attr_name, value) instead. Like this: class Song < ActiveRecord::Base # Uses an integer of seconds to hold the length of the song def length=(minutes) write_attribute("length", minutes * 60) end def length read_attribute("length") / 60 end end The clever kid will notice that this opens a door to sidestep the automated type conversion by using @attributes directly. This is not recommended as read/write_attribute may be granted additional responsibilities in the future, but if you think you know what you're doing and aren't afraid of future consequences, this is an option. * Applied a few minor bug fixes reported by Daniel Von Fange. What about Active Record 1.0.0? ================================== Active Record will be moving to promised land of 1.0.0 within a reasonably short time frame. So if you have any wishes, comments, or complaints, you'll want to voice them sooner rather than later. 1.0.0 won't mean the end of developement, of course, but it would be nice to have a really solid release. So do speak forth. Call for help! ============== Do you have working knowledge with and access to either Oracle, ODBC, Sybase, or DB2, I'd be really grateful if you would consider writing an adapter for Active Record. Adapters are usually just around 100 lines of code. You'll have three examples to look at, a well-specified interface[1], and almost 100 test cases to make it real easy. Luke Holden reports that he spent just a few hours getting SQLite and PostgreSQL adapters working. [1] http://ar.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ AbstractAdapter.html Active Record -- Object-relation mapping put on rails ===================================================== Active Record connects business objects and database tables to create a persistable domain model where logic and data is presented in one wrapping. It's an implementation of the object-relational mapping (ORM) pattern by the same name as described by Martin Fowler: "An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data." Active Records main contribution to the pattern is to relieve the original of two stunting problems: lack of associations and inheritance. By adding a simple domain language-like set of macros to describe the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the gap of functionality between the data mapper and active record approach. A short rundown of the major features: * Automated mapping between classes and tables, attributes and columns. class Product < ActiveRecord::Base; end ...is automatically mapped to the table named "products", such as: CREATE TABLE products ( id int(11) NOT NULL auto_increment, name varchar(255), PRIMARY KEY (id) ); ...which again gives Product#name and Product#name=(new_name) * Associations between objects controlled by simple meta-programming macros. class Firm < ActiveRecord::Base has_many :clients has_one :account belong_to :conglomorate end * Aggregations of value objects controlled by simple meta-programming macros. class Account < ActiveRecord::Base composed_of :balance, :class_name => "Money", :mapping => %w(balance amount) composed_of :address, :mapping => [%w(address_street street), %w(address_city city)] end * Validation rules that can differ for new or existing objects. class Post < ActiveRecord::Base def validate # validates on both creates and updates errors.add_on_empty "title" end def validate_on_update errors.add_on_empty "password" end end * Callbacks as methods or ques on the entire lifecycle (instantiation, saving, destroying, validating, etc). class Person < ActiveRecord::Base def before_destroy # is called just before Person#destroy CreditCard.find(credit_card_id).destroy end end class Account < ActiveRecord::Base after_find :eager_load, 'self.class.announce(#{id})' end Learn more in link:classes/ActiveRecord/Callbacks.html * Observers for the entire lifecycle class CommentObserver < ActiveRecord::Observer def after_create(comment) # is called just after Comment#save NotificationService.send_email("david@loudthinking.com", comment) end end * Inheritance hierarchies class Company < ActiveRecord::Base; end class Firm < Company; end class Client < Company; end class PriorityClient < Client; end * Transaction support on both a database and object level. The latter is implemented by using Transaction::Simple # Just database transaction Account.transaction do david.withdrawal(100) mary.deposit(100) end # Database and object transaction Account.transaction(david, mary) do david.withdrawal(100) mary.deposit(100) end * Direct manipulation (instead of service invocation) So instead of (Hibernate example): long pkId = 1234; DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) ); // something interesting involving a cat... sess.save(cat); sess.flush(); // force the SQL INSERT Active Record lets you: pkId = 1234 cat = Cat.find(pkId) # something even more interesting involving a the same cat... cat.save * Database abstraction through simple adapters (~100 lines) with a shared connector ActiveRecord::Base.establish_connection(:adapter => "sqlite", :dbfile => "dbfile") ActiveRecord::Base.establish_connection( :adapter => "mysql", :host => "localhost", :username => "me", :password => "secret", :database => "activerecord" ) * Logging support for Log4r and Logger ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") Philosophy ========== Active Record attempts to provide a coherent wrapping for the inconvenience that is object-relational mapping. The prime directive for this mapping has been to minimize the amount of code needed to built a real-world domain model. This is made possible by relying on a number of conventions that make it easy for Active Record to infer complex relations and structures from a minimal amount of explicit direction. Convention over Configuration: * No XML-files! * Lots of reflection and run-time extension * Magic is not inherently a bad word Admit the Database: * Lets you drop down to SQL for odd cases and performance * Doesn't attempt to duplicate or replace data definitions -- David Heinemeier Hansson, http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby http://www.basecamphq.com/ -- Web-based Project Management http://www.loudthinking.com/ -- Broadcasting Brain http://www.nextangle.com/ -- Development & Consulting Services