Active Record is an object-relational mapping pattern that allows mapping database tables to object classes. It uses the principle of "convention over configuration" to minimize configuration work. The Active Record pattern is implemented in Ruby on Rails through the ActiveRecord library, which provides methods for CRUD operations and associations between models. It aims to make working with databases and ORM intuitive for developers.
Solution Manual for Principles of Corporate Finance 14th Edition by Richard B...
Introduction to Active Record at MySQL 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 procedures
• 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. 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;
19. 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;
20. 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;
21. 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 users.* FROM users, comments LEFT JOIN comments
ON comments.post_id = users.id;
Returns read only objects unless you say :readonly => false
23. 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
24. 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
25. 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
26. 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"}>
27. 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;
29. 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)
30. 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
31. 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
32. 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"}>]
33. 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=
34. 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)
45. 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
46. 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
47. 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...
48. 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
49. 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
50. 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
51. What AR Returns?
• Arrays of Model Objects
• Preselects and instantiates objects
• Nifty methods: to_yaml, to_xml, to_json
56. 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 (Rails 2.0)
def self.authenticate_safely_simply(user_name, password)
find(:first, :conditions =
{ :user_name = user_name, :password = password })
end
end
57. Use The ActiveRecord Relationships
Anti-Pattern #1: Manually specifying the IDs when you construct the queries;
def show
unless @todo_list = TodoList.find_by_id_and_user_id(params[:id], current_user.id)
redirect_to '/'
end
Anti-Pattern #2: Querying globally, then checking ownership after the fact;
def show
@todo_list = TodoList.find(params[:id])
redirect_to '/' unless @todo_list.user_id = current_user.id
end
Anti-Pattern #3: Abusing with_scope for a this simple case either directly, or in an around_filter.
def show
with_scope(:find={:user_id=current_user.id}) do
@todo_list = TodoList.find(params[:id])
end
end
Best Practice: The most effective way to do this is to call find on the todo_lists association.
def show
@todo_list = current_user.todo_lists.find(params[:id])
end
Examples Stolen From: http://www.therailsway.com/2007/3/26/association-proxies-are-your-friend
58. Create Via Association Proxies
The Bad Way
def create
@todo_list = TodoList.new(params[:todo_list])
@todo_list.user_id = current_user.id
@todo_list.save!
redirect_to todo_list_url(@todo_list)
end
A Better Way: Use association proxies for creation.
def create
@todo_list = current_user.todo_lists.create! params[:todo_list]
redirect_to todo_list_url(@todo_list)
end
The Best Practice - Handle exceptions for the user.
def create
@todo_list = current_user.todo_lists.build params[:todo_list]
if @todo_list.save
redirect_to todo_list_url(@todo_list)
else
render :action='new'
end
end Examples Stolen From: http://www.therailsway.com/2007/3/26/association-proxies-are-your-friend
59. Special Fields
* created_at * #{table_name}_count
* position
* created_on
* parent_id
* updated_at
* lft
* updated_on
* rgt
* lock_version
* quote
* type
* template
* id
60. Table Inheritance
Class Table Inheritance: Represents an inheritance
hierarchy of classes with one table for each class1.
Single Table Inheritance: Represents an inheritance
hierarchy of classes as a single table that has
columns for all the fields of the various classes2.
Concrete Table Inheritance: Represents an
inheritance hierarchy of classes with one table per
concrete class in the hierarchy
61. STI - Single Table Inheritance
Represents an inheritance hierarchy of classes as a single
table that has columns for all the fields of the various classes.
62. STI - Single Table Inheritance
CREATE TABLE `companies` (
class Company ActiveRecord::Base; end
`id` int(11) default NULL,
class Firm Company; end
`name` varchar(255) default NULL,
class Client Company; end `type` varchar(32) default NULL
class PriorityClient Client; end )
Company.find(:first)
SELECT * FROM companies LIMIT 1;
Firm.find(:first)
SELECT * FROM companies WHERE type = ‘firm’ LIMIT 1;
63. Legacy Databases
How to do legacy databases with Active Record?
http://sl33p3r.free.fr/tutorials/rails/legacy/legacy_databases.html
64. Supporting Legacy DB’s
class CustomerNote ActiveRecord::Base
set_primary_key client_comment_id
set_sequence_name FooBarSequences
def self.table_name() client_comment end
def body
read_attribute client_comment_body
end
def body=(value)
write_attribute client_comment_body, value
end
end
Thanks to: http://www.robbyonrails.com/articles/2005/07/25/the-legacy-of-databases-with-rails
65. Changing ActiveRecord
Modify Active Record
ActiveRecord::Base.table_name_prefix = my_
ActiveRecord::Base.table_name_suffix = _table
ActiveRecord::Base.pluralize_table_names = false
Fixing the Auto-Increment / Sequence Problem
module ActiveRecord
module ActiveRecord
module ConnectionAdapters
class Base
class MysqlAdapter
class self
def prefetch_primary_key?(table_name = nil)
def reset_sequence_name
true
#{table_name}_sequence
end
end
end
end
end
end
end
end
Thanks to: http://fora.pragprog.com/rails-recipes/write-your-own/post/84
66. Changing ActiveRecord
Telling ActiveRecord to fetch the primary key
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def prefetch_primary_key?(table_name = nil)
true
end
def next_sequence_value(sequence_name)
sql = UPDATE #{ sequence_name} SET Id=LAST_INSERT_ID(Id+1);
update(sql, #{sequence_name} Update)
select_value(SELECT Id from #{ sequence_name},'Id')
end
end
end
Thanks to: http://fora.pragprog.com/rails-recipes/write-your-own/post/84
67. Ruby on Rails
AR Alternatives
Ruby DataMapper
iBatis - rBatis
68. Ruby DataMapper
http://rubyforge.org/projects/datamapper
class FasterAuthor DataMapper::Base
set_table_name 'authors'
property :name, :string, :size = 100
property :url, :string, :size = 255
property :is_active?, :boolean
property :email, :string, :size = 255
property :hashed_pass, :string, :size = 40
property :created_at, :datetime
property :modified_at, :datetime
has_many :posts, :class = 'FasterPost' # :foreign_key = 'post_id'
# prepends HTTP to a URL if necessary
def self.prepend_http(url = '')
if url and url != '' and not(url =~ /^http/i)
url = 'http://' + url
end
return url
end
end
69. iBatis - rBatis
iBatis for Ruby (RBatis) is a port of Apache's iBatis library to Ruby
and Ruby on Rails. It is an O/R-mapper that allows for complete
customization of SQL. http://ibatis.apache.org
Not Very DRY / Rails Like