Ticket #7747: active_record_readme_revised.diff
| File active_record_readme_revised.diff, 21.4 kB (added by fearoffish, 1 year ago) |
|---|
-
README
old new 1 = Active Record -- Object-relation mapping put on rails1 = Active Record 2 2 3 Active Record connects business objects and database tables to create a persistable 4 domain model where logic and data are presented in one wrapping. It's an implementation 5 of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] 3 Active Record is the glue between your application and the database. In essence, Active Record 4 represents relational data from databases in an object-relational manner. Whenever you want 5 records from the database, Active Record is the class you'll use. Active Record connects 6 business objects and database tables to create a persist-able domain model where logic and 7 data are presented in one wrapping. It's an implementation of the object-relational mapping (ORM) 8 pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] 6 9 by the same name as described by Martin Fowler: 7 10 8 "An object that wraps a row in a database table or view, encapsulates9 the database access, and adds domain logic on that data."11 "An object that wraps a row in a database table or view, encapsulates 12 the database access, and adds domain logic on that data." 10 13 11 14 Active Record's main contribution to the pattern is to relieve the original of two stunting problems: 12 15 lack of associations and inheritance. By adding a simple domain language-like set of macros to describe 13 16 the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the 14 17 gap of functionality between the data mapper and active record approach. 15 18 16 A short rundown of the major features:17 19 18 * Automated mapping between classes and tables, attributes and columns. 20 == Philosophy 19 21 20 class Product < ActiveRecord::Base; end 21 22 ...is automatically mapped to the table named "products", such as: 23 24 CREATE TABLE products ( 25 id int(11) NOT NULL auto_increment, 26 name varchar(255), 27 PRIMARY KEY (id) 28 ); 22 Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is 23 object-relational mapping. The prime directive for this mapping has been to minimize 24 the amount of code needed to build a real-world domain model. This is made possible 25 by relying on a number of conventions that make it easy for Active Record to infer 26 complex relations and structures from a minimal amount of explicit direction. 29 27 30 ...which again gives Product#name and Product#name=(new_name)31 32 {Learn more}[link:classes/ActiveRecord/Base.html]33 28 29 == Overview 34 30 35 * Associations between objects controlled by simple meta-programming macros. 31 When we access our database in Rails we ask an Active Record model to do it for us, Active Record 32 then transforms these requests into SQL queries, executes them on the database and returns the results as 33 either an array or a single object. 36 34 37 class Firm < ActiveRecord::Base 38 has_many :clients 39 has_one :account 40 belongs_to :conglomorate 41 end 35 The tables in your database are best created using the ActiveRecord::Migration class. Migrations are a way of 36 constructing your database schema using ruby methods. They allow you to define the schema in steps 37 which you can use to move both up and down the schema definition. This gives you an easy way to move from 38 development to production with database changes wrapped up in easy to read ruby methods that can be executed 39 when necessary. 42 40 43 {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] 41 Learn more in the ActiveRecord::Migration class file. 44 42 43 == Conventions 45 44 46 * Aggregations of value objects controlled by simple meta-programming macros. 45 ActiveRecord has some conventions that you should be aware of before you use it, they will clear 46 up most queries you have about the basics. 47 47 48 class Account < ActiveRecord::Base 49 composed_of :balance, :class_name => "Money", 50 :mapping => %w(balance amount) 51 composed_of :address, 52 :mapping => [%w(address_street street), %w(address_city city)] 53 end 48 * Models should be singular, camel case and without spaces e.g. Post, Person, Comment 49 * Table names should be plural, lower case and underscored e.g. posts, people, comments 50 * Relationships between models are described as they would be verbally spoken e.g. has_many :posts, belongs_to :post 51 * Foreign keys in tables are assumed to be the name of the foreign object (singular) with _id on the end e.g. if we have posts with many comments, the foreign key for comments would be post_id 52 * Convention over configuration means that certain details are guessed, but they can be overridden if the need arises e.g. If you are working with legacy databases and the foreign keys are already set 54 53 55 {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] 54 Learn more about the methods available to your Active Record models in ActiveRecord::Base. 56 55 56 == Feature Examples 57 57 58 * Validation rules that can differ for new or existing objects. 58 === Database abstraction 59 59 60 class Account < ActiveRecord::Base 61 validates_presence_of :subdomain, :name, :email_address, :password 62 validates_uniqueness_of :subdomain 63 validates_acceptance_of :terms_of_service, :on => :create 64 validates_confirmation_of :password, :email_address, :on => :create 65 end 60 Database abstraction through simple adapters (~100 lines) with a shared connector, Rails configures the database 61 in the environment.rb file using the details you supply in the database.yml file. 66 62 67 {Learn more}[link:classes/ActiveRecord/Validations.html]63 ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") 68 64 65 ActiveRecord::Base.establish_connection( 66 :adapter => "mysql", 67 :host => "localhost", 68 :username => "me", 69 :password => "secret", 70 :database => "activerecord" 71 ) 69 72 70 * Acts that can make records work as lists or trees: 73 {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for 74 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/OCIAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html]. 71 75 72 class Item < ActiveRecord::Base73 belongs_to :list74 acts_as_list :scope => :list75 end76 77 item.move_higher78 item.move_to_bottom79 76 80 Learn about {acts_as_list}[link:classes/ActiveRecord/Acts/List/ClassMethods.html], {the instance methods acts_as_list provides}[link:classes/ActiveRecord/Acts/List/InstanceMethods.html], and 81 {acts_as_tree}[link:classes/ActiveRecord/Acts/Tree/ClassMethods.html] 82 83 * Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc). 77 === Automated mapping 84 78 85 class Person < ActiveRecord::Base 86 def before_destroy # is called just before Person#destroy 87 CreditCard.find(credit_card_id).destroy 88 end 89 end 79 Automated mapping between your Active Record models and database tables, as well as attributes and columns. 90 80 91 class Account < ActiveRecord::Base 92 after_find :eager_load, 'self.class.announce(#{id})' 93 end 81 class Product < ActiveRecord::Base; end 94 82 95 {Learn more}[link:classes/ActiveRecord/Callbacks.html] 83 ...is automatically mapped to the table named "products", such as: 96 84 85 CREATE TABLE products ( 86 id int(11) NOT NULL auto_increment, 87 name varchar(255), 88 PRIMARY KEY (id) 89 ); 97 90 98 * Observers for the entire lifecycle 91 ...which again gives Product#name and Product#name=(new_name), Active Record created the methods 92 because of the existence of the table column "name". 99 93 100 class CommentObserver < ActiveRecord::Observer 101 def after_create(comment) # is called just after Comment#save 102 Notifications.deliver_new_comment("david@loudthinking.com", comment) 103 end 104 end 94 {Learn more}[link:classes/ActiveRecord/Base.html] 95 105 96 106 {Learn more}[link:classes/ActiveRecord/Observer.html] 97 === Associations 107 98 99 Associations between objects controlled by simple meta-programming macros. By describing model 100 relationships with these simple method calls we get a load of functionality for free. 101 102 class Firm < ActiveRecord::Base 103 has_many :clients 104 has_one :account 105 belongs_to :conglomerate 106 end 108 107 109 * Inheritance hierarchies 108 {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] 110 109 111 class Company < ActiveRecord::Base; end112 class Firm < Company; end113 class Client < Company; end114 class PriorityClient < Client; end115 110 116 {Learn more}[link:classes/ActiveRecord/Base.html] 111 === Aggregations 117 112 113 Aggregations of value objects controlled by simple meta-programming macros. 118 114 119 * Transaction support on both a database and object level. The latter is implemented 120 by using Transaction::Simple[http://railsmanual.com/module/Transaction::Simple] 115 class Account < ActiveRecord::Base 116 composed_of :balance, :class_name => "Money", 117 :mapping => %w(balance amount) 118 composed_of :address, 119 :mapping => [%w(address_street street), %w(address_city city)] 120 end 121 122 ...which gives Account#balance and Account#address which return strings of the composed results of :mapping. 121 123 122 # Just database transaction 123 Account.transaction do 124 david.withdrawal(100) 125 mary.deposit(100) 126 end 124 {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] 127 125 128 # Database and object transaction129 Account.transaction(david, mary) do130 david.withdrawal(100)131 mary.deposit(100)132 end133 126 134 {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] 127 === Validations 135 128 129 Validations are a way of determining if a record contains valid details (described like the example below), 130 if a record fails validation it will not save (unless explicitly told to skip validations). 136 131 137 * Reflections on columns, associations, and aggregations 132 Validation rules that can differ for new or existing objects depending on the options you give. 138 133 139 reflection = Firm.reflect_on_association(:clients) 140 reflection.klass # => Client (class) 141 Firm.columns # Returns an array of column descriptors for the firms table 134 class Account < ActiveRecord::Base 135 validates_presence_of :subdomain, :name, :email_address, :password 136 validates_uniqueness_of :subdomain 137 validates_acceptance_of :terms_of_service, :on => :create 138 validates_confirmation_of :password, :email_address, :on => :create 139 end 142 140 143 {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]141 {Learn more}[link:classes/ActiveRecord/Validations.html] 144 142 145 143 146 * Direct manipulation (instead of service invocation) 144 === List/Tree Behaviour 147 145 148 So instead of (Hibernate[http://www.hibernate.org/] example):146 Acts that can make records work as lists or trees: 149 147 150 long pkId = 1234; 151 DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) ); 152 // something interesting involving a cat... 153 sess.save(cat); 154 sess.flush(); // force the SQL INSERT 148 class Item < ActiveRecord::Base 149 belongs_to :list 150 acts_as_list :scope => :list 151 end 155 152 156 Active Record lets you: 153 item.move_higher 154 item.move_to_bottom 157 155 158 pkId = 1234 159 cat = Cat.find(pkId) 160 # something even more interesting involving the same cat... 161 cat.save 156 Learn about {acts_as_list}[link:classes/ActiveRecord/Acts/List/ClassMethods.html], {the instance methods acts_as_list provides}[link:classes/ActiveRecord/Acts/List/InstanceMethods.html], and {acts_as_tree}[link:classes/ActiveRecord/Acts/Tree/ClassMethods.html]. 162 157 163 {Learn more}[link:classes/ActiveRecord/Base.html]164 158 159 === Callbacks 165 160 166 * Database abstraction through simple adapters (~100 lines) with a shared connector 161 Callbacks are called when certain behaviour occurs, they can happen on many different levels of the object 162 lifecycle (instantiation, saving, destroying, validating, etc). They can either be defined inline or given 163 a method name (as a symbol) which will be called. 167 164 168 ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") 165 class Person < ActiveRecord::Base 166 def before_destroy # is called just before Person#destroy 167 CreditCard.find(credit_card_id).destroy 168 end 169 end 170 171 class Account < ActiveRecord::Base 172 after_find :eager_load, 'self.class.announce(#{id})' 173 end 169 174 170 ActiveRecord::Base.establish_connection( 171 :adapter => "mysql", 172 :host => "localhost", 173 :username => "me", 174 :password => "secret", 175 :database => "activerecord" 176 ) 175 {Learn more}[link:classes/ActiveRecord/Callbacks.html] 177 176 178 {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for179 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/OCIAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html].180 177 178 === Observers 181 179 182 * Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] 180 Observers are similar to callbacks but they are written in their own class rather than directly inside the model, 181 this allows functionality that isn't specific to the model to occur without dirtying the model with irrelevant 182 methods. Observers are defined in the same file location as models 183 183 184 ActiveRecord::Base.logger = Logger.new(STDOUT) 185 ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") 184 class CommentObserver < ActiveRecord::Observer 185 def after_create(comment) # is called just after Comment#save 186 Notifications.deliver_new_comment("david@loudthinking.com", comment) 187 end 188 end 186 189 190 {Learn more}[link:classes/ActiveRecord/Observer.html] 187 191 188 == Simple example (1/2): Defining tables and classes (using MySQL)189 192 190 Data definitions are specified only in the database. Active Record queries the database for 191 the column names (that then serves to determine which attributes are valid) on regular 192 object instantiation through the new constructor and relies on the column names in the rows 193 with the finders. 194 195 # CREATE TABLE companies ( 196 # id int(11) unsigned NOT NULL auto_increment, 197 # client_of int(11), 198 # name varchar(255), 199 # type varchar(100), 200 # PRIMARY KEY (id) 201 # ) 193 === Inheritance Hierarchies 202 194 203 Active Record automatically links the "Company" object to the "companies" table 195 As with standard classes, Active Record classes can inherit from a superclass and therefore share functionality 196 with their parent classes. As in the example below where Car inherits from Vehicle. 204 197 205 class Company < ActiveRecord::Base206 has_many :people, :class_name => "Person"207 end208 198 209 class Firm < Company 210 has_many :clients 199 class Vehicle < ActiveRecord::Base 200 belongs_to :user 201 end 211 202 212 def people_with_all_clients 213 clients.inject([]) { |people, client| people + client.people } 214 end 215 end 203 class Car < Vehicle 204 end 205 206 @car = Car.find(:first) 207 @car.user # Functionality gained from the parent class 216 208 217 The foreign_key is only necessary because we didn't use "firm_id" in the data definition 218 219 class Client < Company 220 belongs_to :firm, :foreign_key => "client_of" 221 end 209 {Learn more}[link:classes/ActiveRecord/Base.html] 222 210 223 # CREATE TABLE people (224 # id int(11) unsigned NOT NULL auto_increment,225 # name text,226 # company_id text,227 # PRIMARY KEY (id)228 # )229 211 230 Active Record will also automatically link the "Person" object to the "people" table 212 === Reflections 231 213 232 class Person < ActiveRecord::Base 233 belongs_to :company 234 end 214 Reflections give ActiveRecord the ability to determine the class and other details on columns, associations, 215 and aggregations. 235 216 236 == Simple example (2/2): Using the domain 217 reflection = Firm.reflect_on_association(:clients) 218 reflection.klass # => Client (class) 219 Firm.columns # Returns an array of column descriptors for the firms table 237 220 238 Picking a database connection for all the Active Records 221 {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] 239 222 240 ActiveRecord::Base.establish_connection(241 :adapter => "mysql",242 :host => "localhost",243 :username => "me",244 :password => "secret",245 :database => "activerecord"246 )247 223 248 Create some fixtures 224 === Logging support 249 225 250 firm = Firm.new("name" => "Next Angle") 251 # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm") 252 firm.save 226 Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. 227 Rails uses this support to add to its log files. 253 228 254 client = Client.new("name" => "37signals", "client_of" => firm.id) 255 # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm") 256 client.save 229 ActiveRecord::Base.logger = Logger.new(STDOUT) 230 ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") 257 231 258 Lots of different finders259 232 260 # SQL: SELECT * FROM companies WHERE id = 1 261 next_angle = Company.find(1) 233 == Simple Example without Rails 262 234 263 # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm' 264 next_angle = Firm.find(1) 235 Active Record determines the type of a models attribute by the database type, consider these table definitions: 265 236 266 # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle' 267 next_angle = Company.find(:first, :conditions => "name = 'Next Angle'") 237 CREATE TABLE companies ( 238 id int(11) unsigned NOT NULL auto_increment, 239 client_of int(11), 240 name varchar(255), 241 type varchar(100), 242 PRIMARY KEY (id) 243 ) 244 245 CREATE TABLE people ( 246 id int(11) unsigned NOT NULL auto_increment, 247 name text, 248 company_id text, 249 PRIMARY KEY (id) 250 ) 268 251 269 next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first 252 If we define a class of <tt>Company</tt> which inherits from ActiveRecord::Base we will have access to the 253 attributes of that object which will map to the columns of the database table, we assume you've configured 254 your database connection already. In the <tt>Company</tt> definition below we've told the <tt>Company</tt> 255 model that it should create an attribute called <tt>people</tt> which is an array of <tt>Person's</tt>. 256 We explicitly set the class name with <tt>:class_name => "Person"</tt> because the plural of people would 257 have been assumed to be peoples, which is undesirable in this case. 270 258 271 The supertype, Company, will return subtype instances 259 class Company < ActiveRecord::Base 260 has_many :people, :class_name => "Person" 261 end 262 263 class Person < ActiveRecord::Base 264 belongs_to :company 265 end 266 267 With these simple definitions we can now automatically access all the people in a company like so: 272 268 273 Firm === next_angle 269 company = Company.find(:first) 270 first_person = company.people.first # gives us the first Person object 271 272 There are more examples in ActiveRecord::Base and other child classes. 274 273 275 All the dynamic methods added by the has_many macro276 277 next_angle.clients.empty? # true278 next_angle.clients.size # total number of clients279 all_clients = next_angle.clients280 281 Constrained finds makes access security easier when ID comes from a web-app282 283 # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2284 thirty_seven_signals = next_angle.clients.find(2)285 286 Bi-directional associations thanks to the "belongs_to" macro287 288 thirty_seven_signals.firm.nil? # true289 290 291 == Examples292 293 Active Record ships with a couple of examples that should give you a good feel for294 operating usage. Be sure to edit the <tt>examples/shared_setup.rb</tt> file for your295 own database before running the examples. Possibly also the table definition SQL in296 the examples themselves.297 298 It's also highly recommended to have a look at the unit tests. Read more in link:files/RUNNING_UNIT_TESTS.html299 300 301 == Philosophy302 303 Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is304 object-relational mapping. The prime directive for this mapping has been to minimize305 the amount of code needed to build a real-world domain model. This is made possible306 by relying on a number of conventions that make it easy for Active Record to infer307 complex relations and structures from a minimal amount of explicit direction.308 309 Convention over Configuration:310 * No XML-files!311 * Lots of reflection and run-time extension312 * Magic is not inherently a bad word313 314 Admit the Database:315 * Lets you drop down to SQL for odd cases and performance316 * Doesn't attempt to duplicate or replace data definitions317 318 319 274 == Download 320 275 321 276 The latest version of Active Record can be found at … … 333 288 RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have, 334 289 then use: 335 290 336 % [sudo] gem install activerecord-1.1 0.0.gem291 % [sudo] gem install activerecord-1.15.2.gem 337 292 338 293 You can also install Active Record the old-fashion way with the following command: 339 294