Active Record is an object-relational mapping (ORM) pattern that allows developers to interact with a database using objects rather than SQL queries. It establishes a direct association between classes and database tables, and between class objects and table rows. The key characteristics of Active Record include directly mapping classes to tables, objects to rows, and using finders and setters to encapsulate data access. The Ruby on Rails framework includes an implementation of Active Record to provide data modeling and database access functions.
Introduction to Active Record - Silicon Valley Ruby Conference 2007
1. Introduction to
Active Record
Evan ‘Rabble’ Henshaw-Plath
evan@protest.net - Yahoo! Brickhouse
anarchogeek.com - testingrails.com
2. Active Record is a
Design Pattern
An object that wraps a row in a database table
or view, encapsulates the database access, and
adds domain logic on that data.
3. Active Record
the Pattern
Person
last_name
first_name
dependents_count
insert
update
get_exemption
is_flagged_for_audit?
get_taxable_earnings?
Active Record uses the most obvious approach,
putting data access logic in the domain object.
- Martin Fowler
4. One Class Per Table
The Model Code The Database
class User < ActiveRecord::Base CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
end
`login` varchar(255),
`email` varchar(255),
`crypted_password` varchar(40),
`salt` varchar(40),
`created_at` datetime default NULL,
`updated_at` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
6. There Are Other Ways
To Do it
Active Record is
just one ‘Data Source
Architectural Pattern’
• Table Data Gateway
• Row Data Gateway
• Data Mapper
• The Anti-Patterns
7. Standard Active Record
• Direct mapping to the DB
• Class to table
• Object to row
• Simple, no relationship between objects
• Just a finder method with getters and setters
8. ActiveRecord
the ruby library
Active Record is
a library built
for Ruby on Rails.
Makes CRUD Easy
Create
Read
Update
Delete
9. ActiveRecord
the ruby library
I have never seen an Active Record
implementation as complete
or as useful as rails. - Martin Fowler
10. Rails’ ActiveRecord
• DRY Conventions & Assumptions
• Validations
• Before and after filters
• Database Agnostic (mostly)
• Migrations
• Model relationships
• has_many, belongs_to, etc...
11. What Active
Record Likes
• mapping class names to
table names
• pluralized table names
• integer primary keys
• classname_id foreign keys
• simple schemas
• single table inheritance
12. Active Record
Doesn’t Like
• views
• stored methods
• foreign key constraints
• cascading commits
• split or clustered db’s
• enums
13. The Basics
./app/models/user.rb Loading a user
class User < ActiveRecord::Base
>> user_obj = User.find(2)
end
=> #<User:0x352e8bc
@attributes=
{"salt"=>"d9ef...",
The SQL Log "updated_at"=>"2007-04-19 10:49:15",
"crypted_password"=>"9c1...",
User Load (0.003175) "id"=>"2",
SELECT * FROM users "remember_token"=>"a8d...",
WHERE (users.id = 2) LIMIT 1 "login"=>"rabble",
"created_at"=>"2007-04-19 10:49:15",
"email"=>"evan@protest.net"}>
14. The Find Method
Find is the primary method of Active Record
Examples:
User.find(23)
User.find(:first)
User.find(:all, :offset => 10, :limit => 10)
User.find(:all, :include => [:account, :friends])
User.find(:all, :conditions =>
[“category in (?), categories, :limit => 50)
User.find(:first).articles
15. The Four Ways of Find
Find by id: This can either be a specific id (1), a list of ids (1, 5,
6), or an array of ids ([5, 6, 10]).
Find first: This will return the first record matched by the
options used.
Find all: This will return all the records matched by the options
used.
Indirectly: The find method is used for AR lookups via
associations.
16. Understanding Find
Model#find(:all, { parameters hash }
What Find Does:
* generates sql
* executes sql
* returns an enumerable (array like object)
* creates an AR model object for each row
17. Find with :conditions
:conditions - An SQL fragment like
"administrator = 1" or [ "user_name = ?", username ].
Student.find(:all, :conditions =>
[‘first_name = ? and status = ?’ ‘rabble’, 1])
New Style (Edge Rails Only)
Student.find(:all, :conditions => {:first_name => “rabble”, :status => 1})
SQL Executed:
SELECT * FROM students WHERE (first_name = 'rabble' and status = 1);
18. Doing it Securely
class User < ActiveRecord::Base
def self.authenticate_unsafely(user_name, password)
find(:first, :conditions =>
"user_name = '#{user_name}' AND password = '#{password}'")
end
def self.authenticate_safely(user_name, password)
find(:first, :conditions =>
[ "user_name = ? AND password = ?", user_name, password ])
end
# Edge Rails Only (Next Version of Rails)
def self.authenticate_safely_simply(user_name, password)
find(:first, :conditions =>
{ :user_name => user_name, :password => password })
end
end
19. Order By
:order - An SQL fragment like "created_at DESC,
name".
Student.find(:all, :order => ‘updated_at DESC’)
SQL Executed:
SELECT * FROM users ORDER BY created_at;
20. Group By
:group - An attribute name by which the result
should be grouped. Uses the GROUP BY SQL-clause.
Student.find(:all, :group => ‘graduating_class’)
SQL Executed:
SELECT * FROM users GROUP BY graduating_class;
21. Limit & Offset
:limit - An integer determining the limit on the
number of rows that should be returned.
:offset- An integer determining the offset from
where the rows should be fetched. So at 5, it would
skip the first 4 rows.
Student.find(:all, :limit => 10, :offset => 0)
SQL Executed:
SELECT * FROM users LIMIT 0, 10;
22. Joins
:joins - An SQL fragment for additional joins like "LEFT
JOIN comments ON comments.post_id = id". (Rarely
needed).
Student.find(:all, :join =>
"LEFT JOIN comments ON comments.post_id = id")
SQL Executed:
SELECT * FROM users GROUP BY graduating_class;
Returns read only objects unless you say :readonly => false
24. Associations
The Four Primary Associations
belongs_to
has_one
has_many
has_and_belongs_to_many
class Project < ActiveRecord::Base
belongs_to
:portfolio
has_one
:project_manager
has_many
:milestones
has_and_belongs_to_many
:categories
end
25. Associations
One to One
has_one & belongs_to
Many to One
has_many & belongs_to
Many to Many
has_and_belongs_to_many
has_many :through
26. One to One
Use has_one in the base, and belongs_to in the
associated model.
class Employee < ActiveRecord::Base
has_one :office
end
class Office < ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
27. One To One Example
>> joe_employee = Employee.find_by_first_name('joe')
SELECT * FROM employees
WHERE (employees.`first_name` = 'joe') LIMIT 1
=> #<Employee:0x36beb14 @attributes={"id"=>"1", "first_name"=>"joe",
"last_name"=>"schmo", "created_at"=>"2007-04-21 09:08:59"}>
>> joes_office = joe_employee.office
SELECT * FROM offices WHERE (offices.employee_id = 1) LIMIT 1
=> #<Office:0x36bc06c @attributes={"employee_id"=>"1", "id"=>"1",
"created_at"=>"2007-04-21 09:11:44", "location"=>"A4302"}>
>> joes_office.employee
SELECT * FROM employees WHERE (employees.`id` = 1)
=> #<Employee:0x36b6ef0 @attributes={"id"=>"1", "first_name"=>"joe",
"last_name"=>"schmo", "created_at"=>"2007-04-21 09:08:59"}>
28. belongs_to
One to One Relationship.
Use belong to when the foreign key is in THIS table.
• Post#author (similar to Author.find(author_id) )
• Post#author=(author) (similar to post.author_id =
author.id)
• Post#author? (similar to post.author == some_author)
• Post#author.nil?
• Post#build_author (similar to post.author =
Author.new)
• Post#create_author (similar to post.author = Author;
post.author.save;
30. has_one
One to One Relationship.
Use has_one when the foreign key is in the OTHER table.
• Account#beneficiary (similar to Beneficiary.find
(:first, :conditions => "account_id = #{id}"))
• Account#beneficiary=(beneficiary) (similar to
beneficiary.account_id = account.id; beneficiary.save)
• Account#beneficiary.nil?
• Account#build_beneficiary (similar to Beneficiary.new
("account_id" => id))
• Account#create_beneficiary (similar to b =
Beneficiary.new("account_id" => id); b.save; b)
31. Defining has_one
class Employee < ActiveRecord::Base
# destroys the associated credit card
has_one :credit_card, :dependent => :destroy
# updates the associated records foreign key value to null rather than destroying it
has_one :credit_card, :dependent => :nullify
has_one :last_comment, :class_name => "Comment", :order => "posted_on"
has_one :project_manager, :class_name => "Person",
:conditions => "role = 'project_manager'"
has_one :attachment, :as => :attachable
end
32. One to Many
One-to-many
Use has_many in the base, and belongs_to in
the associated model.
class Manager < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :manager # foreign key - manager_id
end
33. One to Many
>> benevolent_dictator = Manager.find(:first, :conditions => ['name = "DHH"'])
SELECT * FROM managers WHERE (name = "DHH") LIMIT 1
=> #<Manager:0x369b7b8 @attributes={"name"=>"DHH", "id"=>"1",
"created_at"=>"2007-04-21 09:59:24"}>
>> minions = benevolent_dictator.employees
SELECT * FROM employees WHERE (employees.manager_id = 1)
=> [#<Employee:0x36926a4 @attributes={"manager_id"=>"1", "id"=>"1",
"first_name"=>"joe", "last_name"=>"schmo", "created_at"=>"2007-04-21
09:08:59"}>,
#<Employee:0x36925f0 @attributes={"manager_id"=>"1", "id"=>"2",
"first_name"=>"funky", "last_name"=>"monkey", "created_at"=>"2007-04-21
09:58:20"}>]
34. has_many
Augmenting the Model
• Firm#clients (similar to Clients.find :all, :conditions =>
"firm_id = #{id}")
• Firm#clients<<
• Firm#clients.delete
• Firm#client_ids
• Firm#client_ids=
• Firm#clients=
35. has_many
• Firm#client.clear
• Firm#clients.empty? (similar to firm.clients.size
== 0)
• Firm#clients.size (similar to Client.count
"firm_id = #{id}")
• Firm#clients.find (similar to Client.find(id, :conditions
=> "firm_id = #{id}"))
• Firm#clients.build (similar to Client.new
("firm_id" => id))
• Firm#clients.create (similar to c = Client.new
("firm_id" => id); c.save; c)
46. has_many :through
class Appearance < ActiveRecord::Base
belongs_to :dancer
belongs_to :movie
end
class Dancer < ActiveRecord::Base
has_many :appearances, :dependent => true
has_many :movies, :through => :appearances
end
class Movie < ActiveRecord::Base
has_many :appearances, :dependent => true
has_many :dancers, :through => :appearances
end
47. Validations
class User < ActiveRecord::Base
validates_confirmation_of :login, :password
validates_confirmation_of :email,
:message => "should match confirmation"
validates_format_of :email,
:with => /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})/i,
:on = :create
end
48. Validations
• Keeping Data Clean
• In object validation of fields, calculated
validations
• Instead of key constraints
• The database is for storage, the model is for
the business logic
• Kinds of validations, custom validations, etc...
49. But Wait?
• Aren’t format, presence, relationship
validations supposed to be the database’s
job?
• Traditionally, yes.
• ActiveRecord does constraints in the
model, not the database
50. But Why?
• Validations Constraints are Business Logic
• Business logic should be in the model
• It makes things easy
• End users can get useful error messages
• Makes the postback pattern work well
51. Data Integrity?
• It’s still possible to do constraints in the db
• But it’s not as necessary
• Validations are constraints which make
sense in terms of functionality of the app
• The rails ways is to just use validations
• Most DBA’s insist on foreign_key
constraints
52. What AR Returns?
• Enumerable Objects (kind of like arrays)
• Preselects and instantiates objects
• Nifty methods: to_yaml, to_xml, to_json