diff options
Diffstat (limited to 'activerecord/README.rdoc')
-rw-r--r-- | activerecord/README.rdoc | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc new file mode 100644 index 0000000000..0446180207 --- /dev/null +++ b/activerecord/README.rdoc @@ -0,0 +1,336 @@ += 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 are presented in one wrapping. It's an implementation +of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] +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 Record's 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) + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Associations between objects controlled by simple meta-programming macros. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomorate + end + + {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] + + +* 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 + + {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] + + +* Validation rules that can differ for new or existing objects. + + class Account < ActiveRecord::Base + validates_presence_of :subdomain, :name, :email_address, :password + validates_uniqueness_of :subdomain + validates_acceptance_of :terms_of_service, :on => :create + validates_confirmation_of :password, :email_address, :on => :create + end + + {Learn more}[link:classes/ActiveRecord/Validations.html] + +* Callbacks as methods or queues 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}[link:classes/ActiveRecord/Callbacks.html] + + +* Observers for the entire lifecycle + + class CommentObserver < ActiveRecord::Observer + def after_create(comment) # is called just after Comment#save + Notifications.deliver_new_comment("david@loudthinking.com", comment) + end + end + + {Learn more}[link:classes/ActiveRecord/Observer.html] + + +* Inheritance hierarchies + + class Company < ActiveRecord::Base; end + class Firm < Company; end + class Client < Company; end + class PriorityClient < Client; end + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Transactions + + # Database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] + + +* Reflections on columns, associations, and aggregations + + reflection = Firm.reflect_on_association(:clients) + reflection.klass # => Client (class) + Firm.columns # Returns an array of column descriptors for the firms table + + {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] + + +* Direct manipulation (instead of service invocation) + + So instead of (Hibernate[http://www.hibernate.org/] 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 the same cat... + cat.save + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Database abstraction through simple adapters (~100 lines) with a shared connector + + ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") + + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) + + {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html]. + + +* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] + + ActiveRecord::Base.logger = Logger.new(STDOUT) + ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") + + +* Database agnostic schema management with Migrations + + class AddSystemSettings < ActiveRecord::Migration + def self.up + create_table :system_settings do |t| + t.string :name + t.string :label + t.text :value + t.string :type + t.integer :position + end + + SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1 + end + + def self.down + drop_table :system_settings + end + end + + {Learn more}[link:classes/ActiveRecord/Migration.html] + +== Simple example (1/2): Defining tables and classes (using MySQL) + +Data definitions are specified only in the database. Active Record queries the database for +the column names (that then serves to determine which attributes are valid) on regular +object instantiation through the new constructor and relies on the column names in the rows +with the finders. + + # CREATE TABLE companies ( + # id int(11) unsigned NOT NULL auto_increment, + # client_of int(11), + # name varchar(255), + # type varchar(100), + # PRIMARY KEY (id) + # ) + +Active Record automatically links the "Company" object to the "companies" table + + class Company < ActiveRecord::Base + has_many :people, :class_name => "Person" + end + + class Firm < Company + has_many :clients + + def people_with_all_clients + clients.inject([]) { |people, client| people + client.people } + end + end + +The foreign_key is only necessary because we didn't use "firm_id" in the data definition + + class Client < Company + belongs_to :firm, :foreign_key => "client_of" + end + + # CREATE TABLE people ( + # id int(11) unsigned NOT NULL auto_increment, + # name text, + # company_id text, + # PRIMARY KEY (id) + # ) + +Active Record will also automatically link the "Person" object to the "people" table + + class Person < ActiveRecord::Base + belongs_to :company + end + +== Simple example (2/2): Using the domain + +Picking a database connection for all the Active Records + + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) + +Create some fixtures + + firm = Firm.new("name" => "Next Angle") + # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm") + firm.save + + client = Client.new("name" => "37signals", "client_of" => firm.id) + # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm") + client.save + +Lots of different finders + + # SQL: SELECT * FROM companies WHERE id = 1 + next_angle = Company.find(1) + + # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm' + next_angle = Firm.find(1) + + # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle' + next_angle = Company.find(:first, :conditions => "name = 'Next Angle'") + + next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first + +The supertype, Company, will return subtype instances + + Firm === next_angle + +All the dynamic methods added by the has_many macro + + next_angle.clients.empty? # true + next_angle.clients.size # total number of clients + all_clients = next_angle.clients + +Constrained finds makes access security easier when ID comes from a web-app + + # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2 + thirty_seven_signals = next_angle.clients.find(2) + +Bi-directional associations thanks to the "belongs_to" macro + + thirty_seven_signals.firm.nil? # true + + +== Philosophy + +Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is +object-relational mapping. The prime directive for this mapping has been to minimize +the amount of code needed to build 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 + + +== Download + +The latest version of Active Record can be installed with Rubygems: + +* gem install activerecord + +Documentation can be found at + +* http://api.rubyonrails.org + + +== License + +Active Record is released under the MIT license. + + +== Support + +The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record +RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. + +For other information, feel free to ask on the rubyonrails-talk +(http://groups.google.com/group/rubyonrails-talk) mailing list. |