Teisha Foglia, Engineer at TM LtDoutstanding demonstration..convinced me to have a hardlook at my business model..brilliant Teisha http://dashinghealth.com http://healthimplants.com2 years ago
Are you sure you want to
savaActually, because Rails creator works with MySQL, which still isn't a full-featured SQL DBMS. In 2004 it didn't support custom types and triggers, so ActiveRecord reflects these deficiencies.
Introduction to Active Record - Silicon Valley Ruby Conference 2007Presentation Transcript
Introduction to
Active Record
Evan ‘Rabble’ Henshaw-Plath
evan@protest.net - Yahoo! Brickhouse
anarchogeek.com - testingrails.com
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.
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
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;
One Object Per Row
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
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
ActiveRecord
the ruby library
Active Record is
a library built
for Ruby on Rails.
Makes CRUD Easy
Create
Read
Update
Delete
ActiveRecord
the ruby library
I have never seen an Active Record
implementation as complete
or as useful as rails. - Martin Fowler
Rails’ ActiveRecord
• DRY Conventions & Assumptions
• Validations
• Before and after filters
• Database Agnostic (mostly)
• Migrations
• Model relationships
• has_many, belongs_to, etc...
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
Active Record
Doesn’t Like
• views
• stored methods
• foreign key constraints
• cascading commits
• split or clustered db’s
• enums
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"}>
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
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.
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
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);
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
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;
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;
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;
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
Alternative Finds
find_by_sql
find_by_attribute_and_attribute2
find_or_create
Depreciated Find’s
find_first
find_all
find_on_conditions
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
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
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
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"}>
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;
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)
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
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
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"}>]
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=
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)
Many to Many
Simple Joiner Table
has_and_belongs_to_many
Joiner Model
has_many :through
has_and_belongs_to_many
The Simple Joiner Table Way
has_and_belongs_to_many
neglected
by
rails-core
has_and_belongs_to_many
Augmenting the Model
• Developer#projects
• Developer#projects<<
• Developer#projects.delete
• Developer#projects=
• Developer#projects_ids
• Developer#projects_ids=
• Developer#clear
has_and_belongs_to_many
• Developer#projects.empty?
• Developer#projects.size
• Developer#projects.find(id) # Also find(:first / :all)
• Developer#projects.build #(similar to Project.new
("project_id" => id))
• Developer#projects.create (similar to c =
Project.new("project_id" => id); c.save; c)
habtm example
create_table :developers do |t|
t.column :name, :string
t.column :created_at, :datetime
end
create_table :projects do |t|
t.column :name, :string
t.column :created_at, :datetime
end
create_table(:developers_projects, :id => false) do |t|
t.column :developer_id, :integer
t.column :project_id, :integer
end
habtm example
>> d = Developer.find(1)
SELECT * FROM developers WHERE (developers.`id` = 1)
=> #<Developer:0x32bc7dc @attributes={"name"=>"rabble", "id"=>"1",
"created_at"=>nil}>
>> d.projects
SELECT * FROM projects INNER JOIN developers_projects ON projects.id =
developers_projects.project_id WHERE (developers_projects.developer_id = 1 )
=> [#<Project:0x3257cc4
@attributes=
{"name"=>"ragi",
"project_id"=>"1",
"id"=>"1",
"developer_id"=>"1",
"created_at"=>nil}>,
#<Project:0x3257c10
@attributes=
{"name"=>"acts_as_autenticated",
"project_id"=>"3",
"id"=>"3",
"developer_id"=>"1",
"created_at"=>nil}>]
has_many :through
DHH’s
One True Way
of Many to Many
has_many :through
Full Joiner Model
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
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
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...
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
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
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
What AR Returns?
• Enumerable Objects (kind of like arrays)
• Preselects and instantiates objects
• Nifty methods: to_yaml, to_xml, to_json
5 years ago
5 years ago