Your SlideShare is downloading. ×
Where's My SQL? Designing Databases with ActiveRecord Migrations
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Where's My SQL? Designing Databases with ActiveRecord Migrations

4,198
views

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.

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,198
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
133
Comments
0
Likes
2
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Where’s my SQL? Designing Databases with ActiveRecord Migrations Eleanor McHugh Games With Brains
  • 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. 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. on the menu today a basic development environment ActiveRecord Migrations a simple plug-in
  • 5. play along at home you will need: one development system a standard Ruby install the SQLite database a current install of Rails
  • 6. my dev environment MacOS X TextMate kitchen table chair cups of tea
  • 7. standard ruby install visit http://www.ruby-lang.org one-click installer for windows source code for unix RubyGems - http://rubygems.org/
  • 8. SQLite 3 included with MacOS X :) download from http://www.sqlite.org/ install SWiG? http://www.swig.org/ gem install sqlite3-ruby
  • 9. Rails gem install rails --include-dependencies optional: gem install mongrel
  • 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. 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. 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. 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. 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. 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. Migrations part of ActiveRecord support iterative database development expandable Data Definition Language independent of database back-end pure-Ruby solution :)
  • 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. 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. 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. 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. ...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. 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. fun with plug-ins playing with DDLs is fun but what about the data model? plug-ins are great for this
  • 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. 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. 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. 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. conclusions? Migrations simplify data definition plug-ins simplify model definition together they open many possibilities