├── .gitignore ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.rdoc ├── Rakefile ├── init.rb ├── lib ├── persistize.rb └── persistize │ ├── active_record.rb │ ├── railtie.rb │ └── version.rb ├── persistize.gemspec └── test ├── models ├── address.rb ├── company.rb ├── person.rb ├── project.rb ├── task.rb ├── thing.rb └── wadus │ └── thing.rb ├── persistize_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | doc/ 2 | persistize-*.gem 3 | pkg 4 | rdoc 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - gem --version 3 | rvm: 4 | - 2.2.9 5 | - 2.3.6 6 | - 2.4.3 7 | - 2.5.0 8 | gemfile: 9 | - Gemfile 10 | matrix: 11 | allow_failures: 12 | - rvm: 2.5.0 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | persistize (0.5.0) 5 | activerecord (>= 3.1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (4.2.10) 11 | activesupport (= 4.2.10) 12 | builder (~> 3.1) 13 | activerecord (4.2.10) 14 | activemodel (= 4.2.10) 15 | activesupport (= 4.2.10) 16 | arel (~> 6.0) 17 | activesupport (4.2.10) 18 | i18n (~> 0.7) 19 | minitest (~> 5.1) 20 | thread_safe (~> 0.3, >= 0.3.4) 21 | tzinfo (~> 1.1) 22 | arel (6.0.4) 23 | builder (3.2.3) 24 | concurrent-ruby (1.0.5) 25 | i18n (0.9.1) 26 | concurrent-ruby (~> 1.0) 27 | minitest (5.11.1) 28 | rake (13.0.1) 29 | rdoc (6.0.1) 30 | shoulda (3.5.0) 31 | shoulda-context (~> 1.0, >= 1.0.1) 32 | shoulda-matchers (>= 1.4.1, < 3.0) 33 | shoulda-context (1.2.2) 34 | shoulda-matchers (2.8.0) 35 | activesupport (>= 3.0.0) 36 | sqlite3 (1.3.13) 37 | thread_safe (0.3.6) 38 | tzinfo (1.2.4) 39 | thread_safe (~> 0.1) 40 | 41 | PLATFORMS 42 | ruby 43 | 44 | DEPENDENCIES 45 | persistize! 46 | rake 47 | rdoc 48 | shoulda 49 | sqlite3 50 | 51 | BUNDLED WITH 52 | 1.16.0 53 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Luismi Cavallé & Sergio Gil 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | =Persistize 2 | 3 | Persistize is a Rails plugin for easy denormalization. It works just like +memoize+ but it stores the value as an attribute in the database. You only need to write a method with the denormalization logic and a field in the database with the same name of the method. The field will get updated each time the record is saved: 4 | 5 | class Person < ActiveRecord::Base 6 | def full_name 7 | "#{first_name} #{last_name}" 8 | end 9 | 10 | persistize :full_name 11 | end 12 | 13 | ... 14 | 15 | Person.create(:first_name => 'Jimi', :last_name => 'Hendrix') 16 | Person.find_by_full_name('Jimi Hendrix') # # 17 | 18 | ==Dependency 19 | 20 | Sometimes you want to update the field not when the record is changed, but when some other associated records are. For example: 21 | 22 | class Project < ActiveRecord::Base 23 | has_many :tasks 24 | 25 | def completed? 26 | tasks.any? && tasks.all?(&:completed?) 27 | end 28 | 29 | persistize :completed?, :depending_on => :tasks 30 | 31 | named_scope :completed, :conditions => { :completed => true } 32 | end 33 | 34 | class Task < ActiveRecord::Base 35 | belongs_to :project 36 | end 37 | 38 | ... 39 | 40 | project = Project.create(:name => 'Rails') 41 | task = project.tasks.create(:name => 'Make it scale', :completed => false) 42 | Project.completed # [] 43 | 44 | task.update_attributes(:completed => true) 45 | Project.completed # [#] 46 | 47 | You can add more than one dependency using an array: 48 | 49 | persistize :summary, :depending_on => [:projects, :people, :tasks] 50 | 51 | These examples are just some of the possible applications of this pattern, your imagination is the limit =;-) If you can find better examples, please send them to us. 52 | 53 | ==Install 54 | 55 | Just add it to your +Gemfile+: 56 | 57 | gem "persistize" 58 | 59 | And run: 60 | 61 | $ bundle install 62 | 63 | ==To-do 64 | 65 | * Make cache optional (cache can cause records to be inconsistent if changed and not saved so it would be nice to be able to deactivate it) 66 | 67 | Copyright (c) 2008-2014 Luismi Cavallé & Sergio Gil & Paco Guzmán, released under the MIT license 68 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | Rake::TestTask.new do |t| 3 | t.libs << "test" 4 | t.test_files = FileList["test/**/*_test.rb"] 5 | t.verbose = true 6 | end 7 | 8 | task :default => ["test"] 9 | 10 | require "rdoc/task" 11 | RDoc::Task.new do |rd| 12 | rd.main = "README.rdoc" 13 | rd.rdoc_files.include("README.rdoc", "lib/**/*.rb") 14 | rd.rdoc_dir = "rdoc" 15 | end 16 | 17 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'persistize' 2 | -------------------------------------------------------------------------------- /lib/persistize.rb: -------------------------------------------------------------------------------- 1 | module Persistize 2 | end 3 | 4 | if defined?(Rails::Railtie) 5 | require 'persistize/railtie' 6 | elsif defined?(Rails::Initializer) 7 | raise "persistize is not compatible with Rails 2.3 or older" 8 | end 9 | -------------------------------------------------------------------------------- /lib/persistize/active_record.rb: -------------------------------------------------------------------------------- 1 | module Persistize 2 | mattr_accessor :performant 3 | self.performant = true 4 | 5 | module ActiveRecord 6 | module ClassMethods 7 | def persistize(*args) 8 | options = args.extract_options! 9 | 10 | performant = {:performant => ::Persistize.performant}.merge(options)[:performant] 11 | 12 | args.each do |method| 13 | attribute = method.to_s.sub(/\?$/, '') 14 | 15 | original_method = :"_unpersistized_#{attribute}" 16 | update_method = :"_update_#{attribute}" 17 | 18 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 19 | alias #{original_method} #{method} # alias _unpersistized_full_name full_name 20 | # 21 | def #{method} # def full_name 22 | if new_record? || changed? # if new_record? || changed? 23 | #{original_method} # _unpersistized_full_name 24 | else # else 25 | self[:#{attribute}] # self[:full_name] 26 | end # end 27 | end # end 28 | # 29 | before_create :#{update_method} # before_create :_update_full_name 30 | before_update :#{update_method}, :if => :changed? # before_update :_update_full_name, :if => :changed? 31 | # 32 | def #{update_method} # def _update_full_name 33 | self[:#{attribute}] = #{original_method} # self[:full_name] = _unpersistized_full_name 34 | true # return true to avoid canceling the save # true 35 | end # end 36 | # 37 | def #{update_method}! # def _update_full_name! 38 | if #{performant} # if performant 39 | new_#{attribute} = #{original_method} # new_full_name = _unpersistized_full_name 40 | return if new_#{attribute} == self[:#{attribute}] # return if new_full_name == self[:full_name] 41 | update_attribute :#{attribute}, new_#{attribute} # update_attribute :full_name, new_full_name 42 | else # else 43 | #{update_method} # _update_full_name 44 | save! if #{attribute}_changed? # save! if full_name_changed? 45 | end # end 46 | end # end 47 | RUBY 48 | 49 | if options && options[:depending_on] 50 | dependencies = [options[:depending_on]].flatten 51 | 52 | dependencies.each do |dependency| 53 | generate_callback(reflections[dependency.to_s], update_method) 54 | end 55 | end 56 | 57 | end 58 | end 59 | 60 | private 61 | 62 | def generate_callback(association, update_method) 63 | callback_name = :"#{update_method}_in_#{self.to_s.underscore.gsub(/\W/, '_')}_callback" 64 | association_type = "#{association.macro}#{'_through' if association.through_reflection}" 65 | generate_method = :"generate_#{association_type}_callback" 66 | unless respond_to?(generate_method, true) 67 | raise "#{association_type} associations are not supported by persistize" 68 | end 69 | send(generate_method, association, update_method, callback_name) 70 | end 71 | 72 | def generate_has_many_callback(association, update_method, callback_name) 73 | association.klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 74 | def #{callback_name} # def _update_completed_in_project_callback 75 | return true unless parent_id = self[:#{association.foreign_key}] # return true unless parent_id = self[:project_id] 76 | parent = #{self.name}.find(parent_id) # parent = Project.find(parent_id) 77 | parent.#{update_method}! # parent._update_completed! 78 | end # end 79 | after_save :#{callback_name} # after_save :_update_completed_in_project_callback 80 | after_destroy :#{callback_name} # after_destroy :_update_completed_in_project_callback 81 | RUBY 82 | end 83 | 84 | alias_method :generate_has_one_callback, :generate_has_many_callback # implementation is just the same :) 85 | 86 | def generate_has_many_through_callback(association, update_method, callback_name) 87 | association.klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 88 | def #{callback_name} # def _update_completed_in_person_callback 89 | return true unless through_id = self[:#{association.through_reflection.association_foreign_key}] # return true unless through_id = self[:project_id] 90 | through = #{association.through_reflection.class_name}.find(through_id) # through = Project.find(through_id) 91 | return true unless parent_id = through[:#{association.through_reflection.foreign_key}] # return true unless parent_id = self[:person_id] 92 | parent = #{self.name}.find(parent_id) # parent = Person.find(person_id) 93 | parent.#{update_method}! # parent._update_completed! 94 | end # end 95 | after_save :#{callback_name} # after_save :_update_completed_in_person_callback 96 | after_destroy :#{callback_name} # after_destroy :_update_completed_in_person_callback 97 | RUBY 98 | end 99 | 100 | def generate_belongs_to_callback(association, update_method, callback_name) 101 | association.klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 102 | def #{callback_name} # def _update_project_name_in_task_callback 103 | childs = #{self.name}.where({:#{association.foreign_key} => id}) # childs = Task.where({:project_id => id}) 104 | childs.each(&:"#{update_method}!") # childs.each(&:"_update_project_name!") 105 | end # end 106 | after_save :#{callback_name} # after_save :_update_project_name_in_task_callback 107 | after_destroy :#{callback_name} # after_destroy :_update_project_name_in_task_callback 108 | RUBY 109 | end 110 | 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/persistize/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'persistize/active_record' 2 | 3 | module Persistize 4 | class Railtie < Rails::Railtie 5 | initializer "persistize" do |app| 6 | ActiveSupport.on_load :active_record do 7 | extend Persistize::ActiveRecord::ClassMethods 8 | end 9 | end 10 | end 11 | end 12 | 13 | -------------------------------------------------------------------------------- /lib/persistize/version.rb: -------------------------------------------------------------------------------- 1 | module Persistize 2 | VERSION = '0.5.0' 3 | end 4 | -------------------------------------------------------------------------------- /persistize.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | $:.unshift File.expand_path('../lib', __FILE__) 4 | require 'persistize/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "persistize" 8 | s.version = Persistize::VERSION 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Sergio Gil", "Luismi Cavallé", "Paco Guzmán"] 12 | s.email = "ballsbreaking@bebanjo.com" 13 | s.extra_rdoc_files = ["README.rdoc"] 14 | s.files = %w(init.rb MIT-LICENSE persistize.gemspec Rakefile README.rdoc) + Dir.glob("{test,lib/**/*}") 15 | s.homepage = "http://github.com/bebanjo/persistize" 16 | s.rdoc_options = ["--main", "README.rdoc"] 17 | s.require_paths = ["lib"] 18 | s.rubygems_version = "1.6.2" 19 | s.summary = "Easy denormalization for your ActiveRecord models" 20 | s.specification_version = 3 if s.respond_to? :specification_version 21 | 22 | s.add_dependency("activerecord", ">= 3.1.0") 23 | 24 | s.add_development_dependency("shoulda") 25 | s.add_development_dependency("rake") 26 | s.add_development_dependency("rdoc") 27 | s.add_development_dependency("sqlite3") 28 | end 29 | -------------------------------------------------------------------------------- /test/models/address.rb: -------------------------------------------------------------------------------- 1 | class Address < ActiveRecord::Base 2 | 3 | end -------------------------------------------------------------------------------- /test/models/company.rb: -------------------------------------------------------------------------------- 1 | class Company < ActiveRecord::Base 2 | has_many :people 3 | has_many :tasks 4 | 5 | def summary 6 | "#{name} has #{people.count} people and #{tasks.count} tasks" 7 | end 8 | 9 | persistize :summary, :depending_on => [ :people, :tasks ] 10 | end -------------------------------------------------------------------------------- /test/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person < ActiveRecord::Base 2 | 3 | has_many :projects 4 | has_many :tasks, :through => :projects 5 | has_one :address 6 | 7 | def full_name 8 | "#{first_name} #{last_name}" 9 | end 10 | 11 | def initials 12 | "#{first_name.first}#{last_name.first}".upcase 13 | end 14 | 15 | def info 16 | "#{full_name} has #{tasks.count} tasks in #{projects.count} projects" 17 | end 18 | 19 | def city_sentence 20 | address ? "#{first_name} lives in #{address.city}" : "#{first_name} hasn't told us where he lives" 21 | end 22 | 23 | persistize :full_name, :initials 24 | persistize :info, :depending_on => [:projects, :tasks] 25 | persistize :city_sentence, :depending_on => :address 26 | end -------------------------------------------------------------------------------- /test/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ActiveRecord::Base 2 | has_many :tasks 3 | belongs_to :person 4 | 5 | def completed? 6 | tasks.any? && tasks.all?(&:completed?) 7 | end 8 | 9 | persistize :completed?, :depending_on => :tasks 10 | end -------------------------------------------------------------------------------- /test/models/task.rb: -------------------------------------------------------------------------------- 1 | class Task < ActiveRecord::Base 2 | belongs_to :project 3 | 4 | def project_name 5 | project && project.name 6 | end 7 | 8 | persistize :project_name, :depending_on => :project 9 | end -------------------------------------------------------------------------------- /test/models/thing.rb: -------------------------------------------------------------------------------- 1 | class Thing < ActiveRecord::Base 2 | 3 | end -------------------------------------------------------------------------------- /test/models/wadus/thing.rb: -------------------------------------------------------------------------------- 1 | class Wadus::Thing < ActiveRecord::Base 2 | self.table_name = :wadus_things 3 | 4 | has_many :things, :class_name => "::Thing", :foreign_key => "wadus_thing_id" 5 | 6 | def summary 7 | things.map(&:name).join(", ") 8 | end 9 | persistize :summary, :depending_on => :things 10 | end -------------------------------------------------------------------------------- /test/persistize_test.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require File.dirname(__FILE__) + '/test_helper' 3 | 4 | class PersistizeTest < Minitest::Test 5 | 6 | context "Using persistize" do 7 | 8 | setup do 9 | setup_db 10 | end 11 | 12 | teardown do 13 | drop_db 14 | end 15 | 16 | context "a person" do 17 | 18 | setup do 19 | @person = Person.create(:first_name => "Jimi", :last_name => "Hendrix") 20 | end 21 | 22 | should "update the value of full name in the database when created" do 23 | assert_equal('Jimi Hendrix', @person[:full_name]) 24 | assert_equal(@person, Person.find_by_full_name('Jimi Hendrix')) 25 | end 26 | 27 | should "update the value of full name in the database when updated" do 28 | @person.update_attributes!(:first_name => "Axl", :last_name => "Rose") 29 | assert_equal('Axl Rose', @person[:full_name]) 30 | assert_equal(@person, Person.find_by_full_name('Axl Rose')) 31 | end 32 | 33 | should "call the method when object is dirty" do 34 | @person.first_name = 'Axl' 35 | assert_equal('Axl Hendrix', @person.full_name) 36 | end 37 | 38 | should "call the method when reading before being created" do 39 | person = Person.new(:first_name => "Jimi", :last_name => "Hendrix") 40 | assert_equal('Jimi Hendrix', person.full_name) 41 | end 42 | 43 | should "also persistize #initials" do 44 | assert_equal('JH', @person[:initials]) 45 | end 46 | 47 | end 48 | 49 | context "a project with tasks" do 50 | 51 | setup do 52 | @project = Project.create(:name => "Rails") 53 | @task = @project.tasks.create(:completed => true) 54 | end 55 | 56 | should "update @project#completed? when a new task is created" do 57 | Task.create(:project_id => @project.id, :completed => false) 58 | assert !@project.reload[:completed] 59 | end 60 | 61 | should "update @project#completed? when a task is deleted" do 62 | @task.destroy 63 | assert !@project.reload[:completed] 64 | end 65 | 66 | should "update @project#completed? when a task is updated" do 67 | @task.update_attributes!(:completed => false) 68 | assert !@project.reload[:completed] 69 | end 70 | 71 | should "update each task's #project_name when the project name is updated" do 72 | @project.update_attributes!(:name => "Merb") 73 | assert_equal "Merb", @task.reload[:project_name] 74 | end 75 | end 76 | 77 | context "a company with people and tasks" do 78 | 79 | setup do 80 | @company = Company.create(:name => "BeBanjo") 81 | end 82 | 83 | should "update summary when it is created" do 84 | assert_equal("BeBanjo has 0 people and 0 tasks", @company.reload[:summary]) 85 | end 86 | 87 | should "update summary when a person is created" do 88 | @company.people.create(:first_name => 'Bruce', :last_name => 'Dickinson') 89 | assert_equal("BeBanjo has 1 people and 0 tasks", @company.reload[:summary]) 90 | end 91 | 92 | should "update summary when a task created" do 93 | @company.tasks.create 94 | assert_equal("BeBanjo has 0 people and 1 tasks", @company.reload[:summary]) 95 | end 96 | 97 | end 98 | 99 | context "a person with projects and tasks" do 100 | 101 | setup do 102 | @person = Person.create(:first_name => "Enjuto", :last_name => "Mojamuto") 103 | @project = Project.create(:name => "La Hora Chanante", :person => @person) 104 | Project.create(:name => "Wadus", :person => @person) 105 | 3.times { Task.create(:project => @project) } 106 | end 107 | 108 | should "have info" do 109 | assert_equal("Enjuto Mojamuto has 3 tasks in 2 projects", @person.reload[:info]) 110 | end 111 | end 112 | 113 | context "namespaced models" do 114 | 115 | should "access to namespaced models" do 116 | @thing = Wadus::Thing.create 117 | 2.times { |i| @thing.things.create(:name => "Thing number #{i + 1}") } 118 | assert_equal("Thing number 1, Thing number 2", @thing.reload[:summary]) 119 | end 120 | 121 | end 122 | 123 | context "a person with an address" do 124 | 125 | setup do 126 | @person = Person.create!(:first_name => "Tomas", :last_name => "Ujfalusi") 127 | end 128 | 129 | should "update city_sentence when person is created" do 130 | assert_equal "Tomas hasn't told us where he lives", @person[:city_sentence] 131 | end 132 | 133 | should "update city_sentence when address is created" do 134 | @person.create_address(:city => "Madrid") 135 | assert_equal "Tomas lives in Madrid", @person.reload[:city_sentence] 136 | end 137 | 138 | should "update city_sentence when address is updated" do 139 | @address = @person.create_address(:city => "Madrid") 140 | @address.update_attributes!(:city => "Tarancón") 141 | assert_equal "Tomas lives in Tarancón", @person.reload[:city_sentence] 142 | end 143 | 144 | should "update city_sentence when address is deleted" do 145 | @address = @person.create_address(:city => "Madrid") 146 | @address.destroy 147 | assert_equal "Tomas hasn't told us where he lives", @person.reload[:city_sentence] 148 | end 149 | end 150 | end 151 | 152 | end 153 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "bundler/setup" 3 | 4 | require "minitest/autorun" 5 | require 'shoulda' 6 | require 'sqlite3' 7 | require 'active_record' 8 | require 'active_support/all' 9 | 10 | require File.dirname(__FILE__) + '/../init' 11 | require 'persistize/active_record' 12 | ActiveRecord::Base.extend Persistize::ActiveRecord::ClassMethods 13 | 14 | require 'active_support/dependencies' 15 | ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__) + '/models' 16 | 17 | ActiveRecord::Base.configurations = {'test' => {:adapter => 'sqlite3', :database => ':memory:'}} 18 | ActiveRecord::Base.establish_connection(:test) 19 | 20 | ActiveRecord::Schema.verbose = false 21 | 22 | def setup_db 23 | ActiveRecord::Schema.define(:version => 1) do 24 | create_table :people do |t| 25 | t.string :first_name, :last_name, :full_name, :initials, :info, :city_sentence 26 | t.integer :company_id 27 | end 28 | create_table :projects do |t| 29 | t.string :name 30 | t.boolean :completed 31 | t.integer :person_id 32 | end 33 | create_table :tasks do |t| 34 | t.integer :project_id, :company_id 35 | t.boolean :completed 36 | t.string :project_name 37 | end 38 | create_table :companies do |t| 39 | t.string :name, :summary 40 | end 41 | create_table :wadus_things do |t| 42 | t.string :summary 43 | end 44 | create_table :things do |t| 45 | t.integer :wadus_thing_id 46 | t.string :name 47 | end 48 | create_table :addresses do |t| 49 | t.integer :person_id 50 | t.string :city 51 | end 52 | end 53 | end 54 | 55 | def drop_db 56 | ActiveRecord::Base.connection.tables.each do |table| 57 | ActiveRecord::Base.connection.drop_table(table) 58 | end 59 | end 60 | 61 | --------------------------------------------------------------------------------