Your SlideShare is downloading. ×
0
Where’s my SQL?
Designing Databases with ActiveRecord Migrations


                Eleanor McHugh
               Games Wit...
the usual disclaimers

This presentation contains code
That code is probably broken
If that bothers you - fix it
It’s call...
so who the hell am I?
     Eleanor McHugh
     Games With Brains

     eleanor@games-with-brains.com




RINDR - Rewriting...
on the menu today

a basic development environment
ActiveRecord
Migrations
a simple plug-in
play along at home

you will need:
  one development system
  a standard Ruby install
  the SQLite database
  a current in...
my dev environment

             MacOS X
             TextMate
             kitchen table
             chair
             ...
standard ruby install

visit http://www.ruby-lang.org
  one-click installer for windows
  source code for unix
RubyGems - ...
SQLite 3

included with MacOS X :)
download from http://www.sqlite.org/
install SWiG? http://www.swig.org/
gem install sql...
Rails


gem install rails --include-dependencies
optional: gem install mongrel
ActiveRecord?
it’s an Object-Relational Mapper
  Ruby objects are database tables
  their attributes are table columns
  e...
using ActiveRecord
intuitive to use
supports popular database backends
rails generators for the lazy
require “rubygems”
ge...
are tables related?
it wouldn’t be relational if they weren’t!
and here’s an example to prove it...
class User < ActiveRec...
but what about tables?
assumed to exist in your database
class User maps to table users
attribute name maps to column name...
in this case...
 roles_users is a join table
 join tables don’t have id columns

create table users(
     id int unsigned ...
reasons to be tearful

tables defined in SQL schema
have to manually insert the id column
probably database dependent
woul...
Migrations

part of ActiveRecord
support iterative database development
expandable Data Definition Language
independent of...
iterative development?

Ruby encourages agile methods
but SQL is far from agile...
changes to schema have side effects
ris...
the basic DDL
in Ruby and database independent :)
only two methods: up and down
class AddUserTable < ActiveRecord::Migrati...
let’s rev up the pace...
table definitions could be more succinct
# file ‘enhanced_table_definitions.rb’
# inspired by Hob...
let’s rev up the pace...
# file ‘expanded_ddl.rb’
require ‘enhanced_table_definitions’

module ExpandedDDL
    def create_...
...and see the benefits
require ‘expanded_ddl’

class AddUserTable < ActiveRecord::Migration
     extend ExpandedDDL

    ...
run in sequence
rake db:migrate
each migration has a sequence number
migrations are run in this order
a hypothetical examp...
fun with plug-ins


playing with DDLs is fun
but what about the data model?
plug-ins are great for this
what’s in a name?
this plug-in adds names to a model
ActiveRecord::Base.send(:include, ActiveRecord::Acts::Named)

module ...
some DDL goodies

module ExpandedDDL
=begin
    add the following to the module
=end

      def create_named_table table, ...
an updated migration
require ‘expanded_ddl’

class AddUserTable < ActiveRecord::Migration
     extend ExpandedDDL

      d...
and its model
this model invokes acts_as_named
the default is for unique names only
:duplicate_names =>true
indices in the...
conclusions?


Migrations simplify data definition
plug-ins simplify model definition
together they open many possibilities
Upcoming SlideShare
Loading in...5
×

Where's My SQL? Designing Databases with ActiveRecord Migrations

4,249

Published on

A presentation given at RoReXchange in February 2007. Covers some abuses of the ActiveRecord Migrations mechanism along with examples of simple Rails plug-in design.

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
4,249
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
134
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Transcript of "Where's My SQL? Designing Databases with ActiveRecord Migrations"

  1. 1. Where’s my SQL? Designing Databases with ActiveRecord Migrations Eleanor McHugh Games With Brains
  2. 2. the usual disclaimers This presentation contains code That code is probably broken If that bothers you - fix it It’s called a learning experience
  3. 3. so who the hell am I? Eleanor McHugh Games With Brains eleanor@games-with-brains.com RINDR - Rewriting bIND in Ruby RailsMUD Confidential Consultancy
  4. 4. on the menu today a basic development environment ActiveRecord Migrations a simple plug-in
  5. 5. play along at home you will need: one development system a standard Ruby install the SQLite database a current install of Rails
  6. 6. my dev environment MacOS X TextMate kitchen table chair cups of tea
  7. 7. standard ruby install visit http://www.ruby-lang.org one-click installer for windows source code for unix RubyGems - http://rubygems.org/
  8. 8. SQLite 3 included with MacOS X :) download from http://www.sqlite.org/ install SWiG? http://www.swig.org/ gem install sqlite3-ruby
  9. 9. Rails gem install rails --include-dependencies optional: gem install mongrel
  10. 10. ActiveRecord? it’s an Object-Relational Mapper Ruby objects are database tables their attributes are table columns each instance is a table row can be used separately from Rails gem install activerecord
  11. 11. using ActiveRecord intuitive to use supports popular database backends rails generators for the lazy require “rubygems” gem “activerecord” ActiveRecord::Base.establish_connection :adapter => “sqlite3”, :database => “test.db” class User < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of validates_presence_of :password end user = User.create :name => “Eleanor McHugh”, :password => “like_duh!”
  12. 12. are tables related? it wouldn’t be relational if they weren’t! and here’s an example to prove it... class User < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of validates_presence_of :password has_and_belongs_to_many :roles end class Role < ActiveRecord::Base has_and_belongs_to_many :users validates_presence_of :name end Role.create :name => “admin” User.create :name => “Eleanor McHugh”, :password => “like_duh!” User.find_by_name(“Eleanor McHugh).roles << Role.find_by_name(“admin”) Role.find_by_name(“admin”).users.each { |user| puts user.name }
  13. 13. but what about tables? assumed to exist in your database class User maps to table users attribute name maps to column name each instance of User has a unique id create table users( id int unsigned not null auto_increment primary key, name varchar(40) not null, password varchar(16) not null );
  14. 14. in this case... roles_users is a join table join tables don’t have id columns create table users( id int unsigned not null auto_increment primary key, name varchar(40) not null, password varchar(16) not null ); create table roles( id int unsigned not null auto_increment primary key, name varchar(40) not null ); create table roles_users( users_id int not null, roles_id int not null );
  15. 15. reasons to be tearful tables defined in SQL schema have to manually insert the id column probably database dependent would much prefer a Ruby solution
  16. 16. Migrations part of ActiveRecord support iterative database development expandable Data Definition Language independent of database back-end pure-Ruby solution :)
  17. 17. iterative development? Ruby encourages agile methods but SQL is far from agile... changes to schema have side effects risk of inconsistent state in database
  18. 18. the basic DDL in Ruby and database independent :) only two methods: up and down class AddUserTable < ActiveRecord::Migration def self.up create_table :roles, :force => true { |t| t.column :name, :string, :null => false } create_table :users, :force => true do |t| [:name, :full_name].each { |c| t.column c, :string, :null => false } [:email_address_id, :account_status_code_id].each { |c| t.column c, :integer, :null => false } [:profile_id, :stylesheet_id].each { |c| t.column c, :integer } [:password_hash, :password_salt].each { |c| t.column c, :string, :null => false } end create_table :roles_users, :force => true do |t| [:role_id, :user_id].each { |c| t.column c, :integer, :null => false } end [:name, :full_name, :account_status_code_id].each { |c| add_index :users, c } add_index :roles, :name [:role_id, :user_id].each { |c| add_index :roles_users, c } end def self.down [:role_id, :user_id].each { |c| remove_index :roles_users, c } remove_index :roles, :name [:name, :full_name, :account_status_code_id].each { |c| remove_index :users, c } [:roles_users, :roles, :users].each { |t| drop_table t } end end
  19. 19. let’s rev up the pace... table definitions could be more succinct # file ‘enhanced_table_definitions.rb’ # inspired by Hobo module ActiveRecord::ConnectionAdapters class TableDefinition @@known_column_types = [:integer, :float, :decimal, :datetime, :date, :timestamp, :time, :text, :string, :binary, :boolean ] def foreign_key foreign_table, *args column foreign_key_name_for(foreign_table).to_sym, :integer, take_options!(args) end def method_missing name, *args @@known_column_types.include?(name) ? args.each {|type| column type, name, take_options!(args) } : super end def self.foreign_key_name_for table quot;#{Inflector.singularize(table)}_idquot; end private def take_options!(args) args.last.is_a?(Hash) ? args.pop : {} end end end
  20. 20. let’s rev up the pace... # file ‘expanded_ddl.rb’ require ‘enhanced_table_definitions’ module ExpandedDDL def create_timestamped_table table, options = {} create_table table, :force => !options[:no_force] do |t| [:created_at, :modified_at].each { |c| t.datetime c } yield t if block_given? end [:created_at, :modified_at].each { |c| add_index table, c } end def drop_timestamped_table table [:created_at, :modified_at].each { |c| remove_index table, c } drop_table table end def create_join_table primary_table, secondary_table, options = {} table = join_table_name(primary_table, secondary_table) create_timestamped_table(table, options) { |t| t.foreign_key key, :null => false } [primary_key, secondary_key].each { |c| add_foreign_key_index table, c } end def drop_join_table primary_table, secondary_table table = join_table_name(primary_table, secondary_table) [primary_table, secondary_table].each { |c| remove_foreign_key_index table, c } drop_table table end def add_foreign_key_index table, key, options = {} add_index table, foreign_key_name_for(key), options end def remove_foreign_key_index table, key remove_index table, foreign_key_name_for(key) end def join_table_name primary_table, secondary_table (primary_table.to_s < secondary_table.to_s) ? quot;#{primary_table}_#{secondary_table}quot; : quot;#{secondary_table}_#{primary_table}quot; end def foreign_key_name_for table “#{Inflector.singularize(table)}_id” end end
  21. 21. ...and see the benefits require ‘expanded_ddl’ class AddUserTable < ActiveRecord::Migration extend ExpandedDDL def self.up create_timestamped_table :users, :force => true do |t| [:name, :full_name].each { |c| t.string c, :null => false } [:email_addresses, :account_status_codes].each { |key| t.foreign_key key, :null => false } [:profiles, :stylesheets].each { |key| t.foreign_key key } [:password_hash, :password_salt].each { |c| t.string c, :null => false } end add_index :users, :name, :unique => true add_index :users, :full_name add_foreign_key_index :users, :account_status_codes create_table :roles, :force => true { |t| t.column :name, :string, :null => false } add_index :roles, :name create_join_table :roles, :users, :force => true [:role_id, :user_id].each { |c| add_index :roles_users, c } end def self.down [:role_id, :user_id].each { |c| remove_index :roles_users, c } drop_join_table :roles, :users remove_index :roles, :name remove_foreign_key_index :users, :account_status_codes [:name, :full_name].each { |c| remove_index :users, c } [:roles, :users].each { |t| drop_timestamped_table t } end end
  22. 22. run in sequence rake db:migrate each migration has a sequence number migrations are run in this order a hypothetical example from RailsMUD 001_add_account_tables 002_add_character_tables 003_add_action_tables 004_alter_account_tables 005_add_creature_tables
  23. 23. fun with plug-ins playing with DDLs is fun but what about the data model? plug-ins are great for this
  24. 24. what’s in a name? this plug-in adds names to a model ActiveRecord::Base.send(:include, ActiveRecord::Acts::Named) module ActiveRecord module Acts #:nodoc: module Named #:nodoc: def self.included(base) base.extend(ClassMethods) end module ClassMethods def acts_as_named(options = {}) write_inheritable_attribute(:acts_as_named_options, {:from => options[:from]}) class_inheritable_reader :acts_as_named_options validates_presence_of :name validates_uniqueness_of :name unless options[:duplicate_names] include ActiveRecord::Acts::Named::InstanceMethods extend ActiveRecord::Acts::Named::SingletonMethods end end module SingletonMethods end module InstanceMethods end end end end
  25. 25. some DDL goodies module ExpandedDDL =begin add the following to the module =end def create_named_table table, options create_timestamped_table table, take_options!(options) do |t| t.column :name, :string, :null => false yield t if block_given? end add_index table, :name, :unique => !options[:duplicate_names_allowed] end def drop_named_table table remove_index table, :name drop_table table end end
  26. 26. an updated migration require ‘expanded_ddl’ class AddUserTable < ActiveRecord::Migration extend ExpandedDDL def self.up create_named_table :roles, :force => true create_named_table :users, :force => true do |t| t.string :full_name, :null => false [:email_addresses, :account_status_codes].each { |key| t.foreign_key key, :null => false } [:profiles, :stylesheets].each { |key| t.foreign_key key } [:password_hash, :password_salt].each { |c| t.string c, :null => false } end add_index :users, :full_name add_foreign_key_index :users, :account_status_codes create_join_table :roles, :users, :force => true [:role_id, :user_id].each { |c| add_index :roles_users, c } end def self.down [:role_id, :user_id].each { |c| remove_index :roles_users, c } drop_join_table :roles, :users remove_foreign_key_index :users, :account_status_codes remove_index :users, :full_name [:roles, :users].each { |t| drop_named_table t } end end
  27. 27. and its model this model invokes acts_as_named the default is for unique names only :duplicate_names =>true indices in the DDL become relations class User < ActiveRecord::Base acts_as_named validates_presence_of :full_name belongs_to :account_status_code validates_presence_of :account_status_code has_one :profile has_one :stylesheet has_one :email_address validates_presence_of :email_address has_and_belongs_to_many :roles end
  28. 28. conclusions? Migrations simplify data definition plug-ins simplify model definition together they open many possibilities
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×