~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/activerecord/README

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
= Active Record -- Object-relation mapping put on rails
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] 
6
 
by the same name as described by Martin Fowler:
7
 
 
8
 
  "An object that wraps a row in a database table or view, encapsulates 
9
 
       the database access, and adds domain logic on that data."
10
 
 
11
 
Active Record's main contribution to the pattern is to relieve the original of two stunting problems:
12
 
lack of associations and inheritance. By adding a simple domain language-like set of macros to describe
13
 
the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the
14
 
gap of functionality between the data mapper and active record approach.
15
 
 
16
 
A short rundown of the major features:
17
 
 
18
 
* Automated mapping between classes and tables, attributes and columns.
19
 
 
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
 
   );
29
 
 
30
 
   ...which again gives Product#name and Product#name=(new_name) 
31
 
   
32
 
  {Learn more}[link:classes/ActiveRecord/Base.html]
33
 
 
34
 
 
35
 
* Associations between objects controlled by simple meta-programming macros. 
36
 
 
37
 
   class Firm < ActiveRecord::Base
38
 
     has_many   :clients
39
 
     has_one    :account
40
 
     belongs_to :conglomorate
41
 
   end
42
 
 
43
 
  {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
44
 
 
45
 
 
46
 
* Aggregations of value objects controlled by simple meta-programming macros. 
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
54
 
 
55
 
  {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
56
 
 
57
 
 
58
 
* Validation rules that can differ for new or existing objects.
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
66
 
 
67
 
  {Learn more}[link:classes/ActiveRecord/Validations.html]
68
 
 
69
 
* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
70
 
 
71
 
   class Person < ActiveRecord::Base
72
 
     def before_destroy # is called just before Person#destroy
73
 
       CreditCard.find(credit_card_id).destroy
74
 
     end
75
 
   end
76
 
 
77
 
   class Account < ActiveRecord::Base
78
 
     after_find :eager_load, 'self.class.announce(#{id})'
79
 
   end
80
 
 
81
 
  {Learn more}[link:classes/ActiveRecord/Callbacks.html]
82
 
 
83
 
 
84
 
* Observers for the entire lifecycle
85
 
 
86
 
   class CommentObserver < ActiveRecord::Observer
87
 
     def after_create(comment) # is called just after Comment#save
88
 
       Notifications.deliver_new_comment("david@loudthinking.com", comment)
89
 
     end
90
 
   end
91
 
 
92
 
  {Learn more}[link:classes/ActiveRecord/Observer.html]
93
 
 
94
 
 
95
 
* Inheritance hierarchies 
96
 
 
97
 
   class Company < ActiveRecord::Base; end
98
 
   class Firm < Company; end
99
 
   class Client < Company; end
100
 
   class PriorityClient < Client; end
101
 
 
102
 
  {Learn more}[link:classes/ActiveRecord/Base.html]
103
 
 
104
 
 
105
 
* Transactions
106
 
 
107
 
    # Database transaction
108
 
    Account.transaction do
109
 
      david.withdrawal(100)
110
 
      mary.deposit(100)
111
 
    end
112
 
 
113
 
  {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
114
 
 
115
 
 
116
 
* Reflections on columns, associations, and aggregations
117
 
 
118
 
    reflection = Firm.reflect_on_association(:clients)
119
 
    reflection.klass # => Client (class)
120
 
    Firm.columns # Returns an array of column descriptors for the firms table
121
 
 
122
 
  {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
123
 
 
124
 
 
125
 
* Direct manipulation (instead of service invocation)
126
 
 
127
 
  So instead of (Hibernate[http://www.hibernate.org/] example):
128
 
 
129
 
     long pkId = 1234;
130
 
     DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
131
 
     // something interesting involving a cat...
132
 
     sess.save(cat);
133
 
     sess.flush(); // force the SQL INSERT
134
 
 
135
 
  Active Record lets you:
136
 
 
137
 
     pkId = 1234
138
 
     cat = Cat.find(pkId)
139
 
     # something even more interesting involving the same cat...
140
 
     cat.save
141
 
 
142
 
  {Learn more}[link:classes/ActiveRecord/Base.html]
143
 
 
144
 
 
145
 
* Database abstraction through simple adapters (~100 lines) with a shared connector
146
 
 
147
 
   ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
148
 
 
149
 
   ActiveRecord::Base.establish_connection(
150
 
     :adapter  => "mysql", 
151
 
     :host     => "localhost", 
152
 
     :username => "me", 
153
 
     :password => "secret", 
154
 
     :database => "activerecord"
155
 
   )
156
 
 
157
 
  {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for
158
 
  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].
159
 
 
160
 
 
161
 
* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
162
 
 
163
 
    ActiveRecord::Base.logger = Logger.new(STDOUT)
164
 
    ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
165
 
 
166
 
 
167
 
* Database agnostic schema management with Migrations
168
 
 
169
 
    class AddSystemSettings < ActiveRecord::Migration
170
 
      def self.up
171
 
        create_table :system_settings do |t|
172
 
          t.string :name
173
 
          t.string :label
174
 
          t.text :value
175
 
          t.string :type
176
 
          t.integer  :position
177
 
        end
178
 
 
179
 
        SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
180
 
      end
181
 
 
182
 
      def self.down
183
 
        drop_table :system_settings
184
 
      end
185
 
    end
186
 
 
187
 
  {Learn more}[link:classes/ActiveRecord/Migration.html]
188
 
 
189
 
== Simple example (1/2): Defining tables and classes (using MySQL)
190
 
 
191
 
Data definitions are specified only in the database. Active Record queries the database for 
192
 
the column names (that then serves to determine which attributes are valid) on regular
193
 
object instantiation through the new constructor and relies on the column names in the rows
194
 
with the finders.
195
 
 
196
 
   # CREATE TABLE companies (
197
 
   #   id int(11) unsigned NOT NULL auto_increment,
198
 
   #   client_of int(11),
199
 
   #   name varchar(255),
200
 
   #   type varchar(100),
201
 
   #   PRIMARY KEY  (id)
202
 
   # )
203
 
 
204
 
Active Record automatically links the "Company" object to the "companies" table
205
 
 
206
 
   class Company < ActiveRecord::Base
207
 
     has_many :people, :class_name => "Person"
208
 
   end
209
 
 
210
 
   class Firm < Company
211
 
     has_many :clients
212
 
  
213
 
     def people_with_all_clients
214
 
      clients.inject([]) { |people, client| people + client.people }
215
 
     end
216
 
   end
217
 
 
218
 
The foreign_key is only necessary because we didn't use "firm_id" in the data definition
219
 
 
220
 
   class Client < Company
221
 
     belongs_to :firm, :foreign_key => "client_of"
222
 
   end
223
 
 
224
 
   # CREATE TABLE people (
225
 
   #   id int(11) unsigned NOT NULL auto_increment,
226
 
   #   name text,
227
 
   #   company_id text,
228
 
   #   PRIMARY KEY  (id)
229
 
   # )
230
 
 
231
 
Active Record will also automatically link the "Person" object to the "people" table
232
 
 
233
 
   class Person < ActiveRecord::Base
234
 
     belongs_to :company
235
 
   end
236
 
 
237
 
== Simple example (2/2): Using the domain
238
 
 
239
 
Picking a database connection for all the Active Records
240
 
 
241
 
   ActiveRecord::Base.establish_connection(
242
 
     :adapter  => "mysql", 
243
 
     :host     => "localhost", 
244
 
     :username => "me", 
245
 
     :password => "secret", 
246
 
     :database => "activerecord"
247
 
   )
248
 
 
249
 
Create some fixtures
250
 
 
251
 
   firm = Firm.new("name" => "Next Angle")
252
 
   # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm")
253
 
   firm.save
254
 
 
255
 
   client = Client.new("name" => "37signals", "client_of" => firm.id)
256
 
   # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm")
257
 
   client.save
258
 
 
259
 
Lots of different finders
260
 
 
261
 
   # SQL: SELECT * FROM companies WHERE id = 1
262
 
   next_angle = Company.find(1)
263
 
 
264
 
   # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm'
265
 
   next_angle = Firm.find(1)    
266
 
 
267
 
   # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle'
268
 
   next_angle = Company.find(:first, :conditions => "name = 'Next Angle'")
269
 
 
270
 
   next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first
271
 
 
272
 
The supertype, Company, will return subtype instances
273
 
 
274
 
   Firm === next_angle
275
 
 
276
 
All the dynamic methods added by the has_many macro
277
 
 
278
 
  next_angle.clients.empty?  # true
279
 
  next_angle.clients.size    # total number of clients
280
 
  all_clients = next_angle.clients
281
 
 
282
 
Constrained finds makes access security easier when ID comes from a web-app
283
 
 
284
 
   # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2
285
 
   thirty_seven_signals = next_angle.clients.find(2)
286
 
 
287
 
Bi-directional associations thanks to the "belongs_to" macro
288
 
 
289
 
   thirty_seven_signals.firm.nil? # true
290
 
 
291
 
 
292
 
== Philosophy 
293
 
 
294
 
Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is 
295
 
object-relational mapping. The prime directive for this mapping has been to minimize
296
 
the amount of code needed to build a real-world domain model. This is made possible
297
 
by relying on a number of conventions that make it easy for Active Record to infer
298
 
complex relations and structures from a minimal amount of explicit direction.
299
 
 
300
 
Convention over Configuration:
301
 
* No XML-files!
302
 
* Lots of reflection and run-time extension
303
 
* Magic is not inherently a bad word 
304
 
 
305
 
Admit the Database:
306
 
* Lets you drop down to SQL for odd cases and performance
307
 
* Doesn't attempt to duplicate or replace data definitions
308
 
 
309
 
 
310
 
== Download
311
 
 
312
 
The latest version of Active Record can be found at
313
 
 
314
 
* http://rubyforge.org/project/showfiles.php?group_id=182
315
 
 
316
 
Documentation can be found at 
317
 
 
318
 
* http://ar.rubyonrails.com
319
 
 
320
 
 
321
 
== Installation
322
 
 
323
 
The prefered method of installing Active Record is through its GEM file. You'll need to have
324
 
RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have,
325
 
then use:
326
 
 
327
 
  % [sudo] gem install activerecord-1.10.0.gem
328
 
 
329
 
You can also install Active Record the old-fashioned way with the following command:
330
 
 
331
 
  % [sudo] ruby install.rb
332
 
 
333
 
from its distribution directory.
334
 
 
335
 
 
336
 
== License
337
 
 
338
 
Active Record is released under the MIT license.
339
 
 
340
 
 
341
 
== Support
342
 
 
343
 
The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record
344
 
RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:
345
 
 
346
 
   Feel free to submit commits or feature requests.  If you send a patch,
347
 
   remember to update the corresponding unit tests.  If fact, I prefer
348
 
   new feature to be submitted in the form of new unit tests.
349
 
 
350
 
For other information, feel free to ask on the rubyonrails-talk 
351
 
(http://groups.google.com/group/rubyonrails-talk) mailing list.