mini_record-compat 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - rbx-2.0
6
+ - ruby-head
7
+ - ree
8
+ notifications:
9
+ recipients:
10
+ - d.dagostino@lipsiasoft.com
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mini_record.gemspec
4
+ gem "rake"
5
+ gem "minitest"
6
+ gem "sqlite3"
7
+ gem 'mysql'
8
+ gem 'sqlite3-ruby'
9
+ gem 'pg'
10
+
11
+ gemspec
data/README.md ADDED
@@ -0,0 +1,167 @@
1
+ [![Build Status](https://secure.travis-ci.org/DAddYE/mini_record.png)](http://travis-ci.org/DAddYE/mini_record)
2
+
3
+
4
+ MiniRecord is a micro extension for our `ActiveRecord` gem.
5
+ With MiniRecord you can add the ability to create columns outside the default `schema.rb`, directly
6
+ in your **model** in a similar way that should know in others projects
7
+ like DataMapper, MongoMapper or MongoID.
8
+
9
+ My inspiration come from this handy [project](https://github.com/pjhyett/auto_migrations).
10
+
11
+ ## Features
12
+
13
+ * Define columns/properties inside your model
14
+ * Perform migrations automatically
15
+ * Auto upgrade your schema, so if you know what you are doing you don't lost your existing data!
16
+ * Add, Remove, Change Columns; Add, Remove, Change indexes
17
+
18
+ ## Instructions
19
+
20
+ What you need is to move/remove your `db/schema.rb`.
21
+ This avoid conflicts.
22
+
23
+ Add to your `Gemfile`:
24
+
25
+ ``` rb
26
+ gem 'mini_record'
27
+ ```
28
+
29
+ That's all!
30
+
31
+ ## Examples
32
+
33
+ Remember that inside properties you can use all migrations methods,
34
+ see [documentation](http://api.rubyonrails.org/classes/ActiveRecord/Migration.html)
35
+
36
+ ``` rb
37
+ class Post < ActiveRecord::Base
38
+ col :title_en, :title_jp
39
+ col :description_en, :description_jp, :as => :text
40
+ col :permalink, :index => true, :limit => 50
41
+ col :comments_count, :as => :integer
42
+ col :category, :as => :references, :index => true
43
+ end
44
+ Post.auto_upgrade!
45
+ ```
46
+
47
+ If you don't like `col` there are also few aliases: `key, field, property, attribute`
48
+
49
+ Instead of `:as => :my_type` you can use `:type => :my_type`
50
+
51
+ Option `:as` or `:type` if not provided is `:string` by default, you can use all ActiveRecord types:
52
+
53
+ ``` rb
54
+ :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean
55
+ :references, :belongs_to, :polymorphic, :timestamp
56
+ ```
57
+
58
+ You can provide others ActiveRecord options like:
59
+
60
+ ``` rb
61
+ :limit, :default, :null, :precision, :scale
62
+
63
+ # example
64
+ class Foo < ActiveRecord::Base
65
+ col :title, :default => "MyTitle" # :as => :string is by default
66
+ col :price, :as => :decimal, :scale => 8, :precision => 2
67
+ end
68
+ ```
69
+
70
+ See [ActiveRecord::TableDefinition](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
71
+ for more details.
72
+
73
+ Finally, when you execute `MyModel.auto_upgrade!`, missing columns, indexes and tables will be created on the fly.
74
+ Indexes and columns present in the db but **not** in your model schema will be **deleted*** also in your db.
75
+
76
+ ### Single Table Inheritance
77
+
78
+ MiniRecord as ActiveRecord support STI plus some goodness, see our specs for more details.
79
+
80
+ ### Adding a new column
81
+
82
+ Super easy, open your model and just add it:
83
+
84
+ ``` rb
85
+ class Post < ActiveRecord::Base
86
+ col :title
87
+ col :body, :as => :text # <<- this
88
+ col :permalink, :index => true
89
+ col :comments_count, :as => :integer
90
+ col :category, :as => :references, :index => true
91
+ end
92
+ Post.auto_upgrade!
93
+ ```
94
+
95
+ So now when you invoke `MyModel.auto_upgrade!` you should see a SQL query like `ALTER TABLE` that mean that your existing
96
+ records are happy and safe.
97
+
98
+ ### Removing a column
99
+
100
+ It's exactly the same, but the column will be _really_ deleted without affect other columns.
101
+
102
+ ### Change columns
103
+
104
+ It's not possible for us know when/what column you have renamed, but we can know if you changed the `type` so
105
+ if you change `t.string :name` to `t.text :name` we are be able to perform an `ALTER TABLE`
106
+
107
+ ### Add/Remove indexes
108
+
109
+ In the same ways we manage columns MiniRecord will detect new indexes and indexes that needs to be removed.
110
+ So when you perform `MyModel.auto_upgrade!` a SQL command like:
111
+
112
+ ``` SQL
113
+ PRAGMA index_info('index_people_on_name')
114
+ CREATE INDEX "index_people_on_surname" ON "people" ("surname")
115
+ ```
116
+
117
+ Note that writing it in DSL way you have same options as `add_index` so you are be able to write:
118
+
119
+ ``` rb
120
+ class Fox < ActiveRecord::Base
121
+ col :foo, :index => true
122
+ col :foo, :index => :custom_name
123
+ col :foo, :index => [:foo, :bar]
124
+ col :foo, :index => { :column => [:branch_id, :party_id], :unique => true, :name => 'by_branch_party' }
125
+ end
126
+ ```
127
+
128
+ That is the same of:
129
+
130
+ ``` rb
131
+ class Fox < ActiveRecord::Base
132
+ col :foo
133
+ add_index :foo
134
+ add_index :custom_name
135
+ add_index [:foo, :bar]
136
+ add_index [:branch_id, :party_id], :unique => true, :name => 'by_branch_party'
137
+ end
138
+ ```
139
+
140
+ ## Warnings
141
+
142
+ This software is not super well tested in a production project.
143
+ Im stated to using it in two customer's projects without any problem.
144
+
145
+ ## TODO
146
+
147
+ * reduce the number of methods we add to ActiveRecord::Base objects
148
+
149
+ ## Author
150
+
151
+ DAddYE, you can follow me on twitter [@daddye](http://twitter.com/daddye) or take a look at my site [daddye.it](http://www.daddye.it)
152
+
153
+ ## Copyright
154
+
155
+ Copyright (C) 2011 Davide D'Agostino - [@daddye](http://twitter.com/daddye)
156
+
157
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
158
+ associated documentation files (the “Software”), to deal in the Software without restriction, including without
159
+ limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
160
+ and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
161
+
162
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
163
+
164
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
165
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
166
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
167
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake"
3
+ require "rake/testtask"
4
+
5
+ %w(install release).each do |task|
6
+ Rake::Task[task].enhance do
7
+ sh "rm -rf pkg"
8
+ end
9
+ end
10
+
11
+ desc "Bump version on github"
12
+ task :bump do
13
+ if `git status -s`.strip == ""
14
+ puts "\e[31mNothing to commit (working directory clean)\e[0m"
15
+ else
16
+ version = Bundler.load_gemspec(Dir[File.expand_path('../*.gemspec', __FILE__)].first).version
17
+ sh "git add .; git commit -a -m \"Bump to version #{version}\""
18
+ end
19
+ end
20
+
21
+ task :release => :bump
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'spec'
24
+ test.test_files = Dir['spec/**/*_spec.rb']
25
+ test.verbose = true
26
+ end
27
+
28
+ task :test_each_db_adapter do
29
+ %w{ mysql sqlite3 postgresql }.each do |db_adapter|
30
+ puts
31
+ puts "#{'*'*10} Running #{db_adapter} tests"
32
+ puts
33
+ puts `bundle exec rake test TEST=spec/#{db_adapter}_spec.rb`
34
+ end
35
+ end
36
+
37
+ task :default => :test_each_db_adapter
38
+ task :spec => :test_each_db_adapter
@@ -0,0 +1,220 @@
1
+ require 'zlib'
2
+ module MiniRecord
3
+ module AutoSchema
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def table_definition
11
+ return superclass.table_definition unless superclass == ActiveRecord::Base
12
+
13
+ @_table_definition ||= begin
14
+ ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
15
+ end
16
+ end
17
+
18
+ def indexes
19
+ return superclass.indexes unless superclass == ActiveRecord::Base
20
+
21
+ @_indexes ||= {}
22
+ end
23
+
24
+ def col(*args)
25
+ return unless connection?
26
+
27
+ options = args.extract_options!
28
+ type = options.delete(:as) || options.delete(:type) || :string
29
+ args.each do |column_name|
30
+ table_definition.send(type, column_name, options)
31
+ column_name = table_definition.columns[-1].name
32
+ case index_name = options.delete(:index)
33
+ when Hash
34
+ add_index(options.delete(:column) || column_name, index_name)
35
+ when TrueClass
36
+ add_index(column_name)
37
+ when String, Symbol, Array
38
+ add_index(index_name)
39
+ end
40
+ end
41
+ end
42
+ alias :key :col
43
+ alias :property :col
44
+ alias :field :col
45
+ alias :attribute :col
46
+
47
+ def reset_table_definition!
48
+ @_table_definition = nil
49
+ end
50
+ alias :reset_schema! :reset_table_definition!
51
+
52
+ def schema
53
+ reset_table_definition!
54
+ yield table_definition
55
+ table_definition
56
+ end
57
+ alias :keys :schema
58
+ alias :properties :schema
59
+ alias :fields :schema
60
+ alias :attributes :schema
61
+
62
+ def add_index(column_name, options={})
63
+ index_name = shorten_index_name connection.index_name(table_name, :column => column_name)
64
+ indexes[index_name] = options.merge(:column => column_name, :name => index_name)
65
+ index_name
66
+ end
67
+ alias :index :add_index
68
+
69
+ def connection?
70
+ !!connection
71
+ rescue Exception => e
72
+ puts "\e[31m%s\e[0m" % e.message.strip
73
+ false
74
+ end
75
+
76
+ def shorten_index_name(name)
77
+ if name.length < connection.index_name_length
78
+ name
79
+ else
80
+ name[0..(connection.index_name_length-11)] + ::Zlib.crc32(name).to_s
81
+ end
82
+ end
83
+
84
+ def sqlite?
85
+ connection.adapter_name =~ /sqlite/i
86
+ end
87
+
88
+ def mysql?
89
+ connection.adapter_name =~ /mysql/i
90
+ end
91
+
92
+ def postgresql?
93
+ connection.adapter_name =~ /postgresql/i
94
+ end
95
+
96
+ def auto_upgrade!(create_table_options = '')
97
+ return unless connection?
98
+
99
+ # normally activerecord's mysql adapter does this
100
+ if mysql?
101
+ create_table_options ||= 'ENGINE=InnoDB'
102
+ end
103
+
104
+ non_standard_primary_key = if (primary_key_column = table_definition.columns.detect { |column| column.name.to_s == primary_key.to_s })
105
+ primary_key_column.type != :primary_key
106
+ end
107
+
108
+ unless non_standard_primary_key
109
+ table_definition.column :id, :primary_key
110
+ end
111
+
112
+ # Table doesn't exist, create it
113
+ unless connection.table_exists? table_name
114
+
115
+ # avoid using connection.create_table because in 3.0.x it ignores table_definition
116
+ # and it also is too eager about adding a primary key column
117
+ create_sql = "CREATE TABLE "
118
+ create_sql << "#{quoted_table_name} ("
119
+ create_sql << table_definition.to_sql
120
+ create_sql << ") #{create_table_options}"
121
+ connection.execute create_sql
122
+
123
+ if non_standard_primary_key
124
+ if sqlite?
125
+ add_index primary_key, :unique => true
126
+ elsif mysql? or postgresql?
127
+ # can't use add_index method because it won't let you do "PRIMARY KEY"
128
+ connection.execute "ALTER TABLE #{quoted_table_name} ADD PRIMARY KEY (#{quoted_primary_key})"
129
+ else
130
+ raise RuntimeError, "mini_record doesn't support non-standard primary keys for the #{connection.adapter_name} adapter!"
131
+ end
132
+ end
133
+
134
+ reset_column_information
135
+ end
136
+
137
+ # Add to schema inheritance column if necessary
138
+ if descendants.present? && !table_definition.columns.any? { |column| column.name.to_s == inheritance_column.to_s }
139
+ table_definition.column inheritance_column, :string
140
+ end
141
+
142
+ # Grab database columns
143
+ fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
144
+ hash[column.name] = column
145
+ hash
146
+ end
147
+
148
+ # Grab new schema
149
+ fields_in_schema = table_definition.columns.inject({}) do |hash, column|
150
+ hash[column.name.to_s] = column
151
+ hash
152
+ end
153
+
154
+ # Remove fields from db no longer in schema
155
+ (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
156
+ column = fields_in_db[field]
157
+ connection.remove_column table_name, column.name
158
+ end
159
+
160
+ # Add fields to db new to schema
161
+ (fields_in_schema.keys - fields_in_db.keys).each do |field|
162
+ column = fields_in_schema[field]
163
+ options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
164
+ options[:default] = column.default if !column.default.nil?
165
+ options[:null] = column.null if !column.null.nil?
166
+ connection.add_column table_name, column.name, column.type.to_sym, options
167
+ end
168
+
169
+ # Change attributes of existent columns
170
+ (fields_in_schema.keys & fields_in_db.keys).each do |field|
171
+ if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
172
+ changed = false # flag
173
+ new_type = fields_in_schema[field].type.to_sym
174
+ new_attr = {}
175
+
176
+ # First, check if the field type changed
177
+ if fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym
178
+ changed = true
179
+ end
180
+
181
+ # Special catch for precision/scale, since *both* must be specified together
182
+ # Always include them in the attr struct, but they'll only get applied if changed = true
183
+ new_attr[:precision] = fields_in_schema[field][:precision]
184
+ new_attr[:scale] = fields_in_schema[field][:scale]
185
+
186
+ # Next, iterate through our extended attributes, looking for any differences
187
+ # This catches stuff like :null, :precision, etc
188
+ fields_in_schema[field].each_pair do |att,value|
189
+ next if att == :type or att == :base or att == :name # special cases
190
+ if !value.nil? && value != fields_in_db[field].send(att)
191
+ new_attr[att] = value
192
+ changed = true
193
+ end
194
+ end
195
+
196
+ # Change the column if applicable
197
+ connection.change_column table_name, field, new_type, new_attr if changed
198
+ end
199
+ end
200
+
201
+ # Remove old index
202
+ indexes_in_db = connection.indexes(table_name).map(&:name)
203
+ (indexes_in_db - indexes.keys).each do |name|
204
+ connection.remove_index(table_name, :name => name)
205
+ end
206
+
207
+ # Add indexes
208
+ indexes.each do |name, options|
209
+ options = options.dup
210
+ unless connection.indexes(table_name).detect { |i| i.name == name }
211
+ connection.add_index(table_name, options.delete(:column), options)
212
+ end
213
+ end
214
+
215
+ # Reload column information
216
+ reset_column_information
217
+ end
218
+ end # ClassMethods
219
+ end # AutoSchema
220
+ end # MiniRecord
@@ -0,0 +1,3 @@
1
+ module MiniRecord
2
+ VERSION = "0.2.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'active_record'
3
+ require 'mini_record/auto_schema'
4
+
5
+ ActiveRecord::Base.send(:include, MiniRecord::AutoSchema)
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "mini_record/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mini_record-compat"
7
+ s.version = MiniRecord::VERSION
8
+ s.authors = ["Davide D'Agostino", "Seamus Abshere"]
9
+ s.email = ["d.dagostino@lipsiasoft.com", "seamus@abshere.net"]
10
+ s.homepage = "https://github.com/seamusabshere/mini_record"
11
+ s.summary = %q{Alternate gem published by Seamus Abshere for ActiveRecord 3.0 support. MiniRecord is a micro gem that allow you to write schema inside your model as you can do in DataMapper.}
12
+ s.description = %q{
13
+ With it you can add the ability to create columns outside the default schema, directly
14
+ in your model in a similar way that you just know in others projects
15
+ like DataMapper or MongoMapper.
16
+ }.gsub(/^ {4}/, '')
17
+
18
+ # s.rubyforge_project = "mini_record"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_runtime_dependency "activerecord", ">=3"
26
+
27
+ # dev dependencies appear to be in the Gemfile
28
+ end
data/spec/models.rb ADDED
@@ -0,0 +1,70 @@
1
+ # be sure to set up activerecord before you require this helper
2
+
3
+ class Person < ActiveRecord::Base
4
+ include SpecHelper
5
+ schema do |s|
6
+ s.string :name
7
+ end
8
+ end
9
+
10
+ class Post < ActiveRecord::Base
11
+ include SpecHelper
12
+
13
+ key :title
14
+ key :body
15
+ key :category, :as => :references
16
+ belongs_to :category
17
+ end
18
+
19
+ class Category < ActiveRecord::Base
20
+ include SpecHelper
21
+
22
+ key :title
23
+ has_many :posts
24
+ end
25
+
26
+ class Animal < ActiveRecord::Base
27
+ include SpecHelper
28
+
29
+ key :name, :index => true
30
+ index :id
31
+ end
32
+
33
+ class Pet < ActiveRecord::Base
34
+ include SpecHelper
35
+
36
+ key :name, :index => true
37
+ end
38
+ class Dog < Pet; end
39
+ class Cat < Pet; end
40
+
41
+ class Vegetable < ActiveRecord::Base
42
+ include SpecHelper
43
+
44
+ set_primary_key :latin_name
45
+
46
+ col :latin_name
47
+ col :common_name
48
+ end
49
+
50
+ class User < ActiveRecord::Base
51
+ include SpecHelper
52
+ col :name
53
+ col :surname
54
+ col :role
55
+ set_inheritance_column :role
56
+ end
57
+ class Administrator < User; end
58
+ class Customer < User; end
59
+
60
+ class Fake < ActiveRecord::Base
61
+ include SpecHelper
62
+ col :name, :surname
63
+ col :category, :group, :as => :references
64
+ end
65
+
66
+ class AutomobileMakeModelYearVariant < ActiveRecord::Base
67
+ include SpecHelper
68
+ col :make_model_year_name
69
+ add_index :make_model_year_name
70
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../spec_helper.rb', __FILE__)
2
+
3
+ bin = ENV['TEST_MYSQL_BIN'] || 'mysql'
4
+ username = ENV['TEST_MYSQL_USERNAME'] || 'root'
5
+ password = ENV['TEST_MYSQL_PASSWORD'] || 'password'
6
+ database = ENV['TEST_MYSQL_DATABASE'] || 'test_mini_record'
7
+ cmd = "#{bin} -u #{username} -p#{password}"
8
+
9
+ `#{cmd} -e 'show databases'`
10
+ unless $?.success?
11
+ $stderr.puts "Skipping mysql tests because `#{cmd}` doesn't work"
12
+ exit 0
13
+ end
14
+
15
+ system %{#{cmd} -e "drop database #{database}"}
16
+ system %{#{cmd} -e "create database #{database}"}
17
+
18
+ ActiveRecord::Base.establish_connection(
19
+ 'adapter' => 'mysql',
20
+ 'encoding' => 'utf8',
21
+ 'database' => database,
22
+ 'username' => username,
23
+ 'password' => password
24
+ )
25
+
26
+ # require 'logger'
27
+ # ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
28
+
29
+ require File.expand_path('../models.rb', __FILE__)
30
+
31
+ require File.expand_path('../shared_examples.rb', __FILE__)
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../spec_helper.rb', __FILE__)
2
+
3
+ createdb_bin = ENV['TEST_CREATEDB_BIN'] || 'createdb'
4
+ dropdb_bin = ENV['TEST_DROPDB_BIN'] || 'dropdb'
5
+ username = ENV['TEST_POSTGRES_USERNAME'] || `whoami`.chomp
6
+ # password = ENV['TEST_POSTGRES_PASSWORD'] || 'password'
7
+ database = ENV['TEST_POSTGRES_DATABASE'] || 'test_mini_record'
8
+
9
+ system %{#{dropdb_bin} #{database}}
10
+ system %{#{createdb_bin} #{database}}
11
+
12
+ ActiveRecord::Base.establish_connection(
13
+ 'adapter' => 'postgresql',
14
+ 'encoding' => 'utf8',
15
+ 'database' => database,
16
+ 'username' => username,
17
+ # 'password' => password
18
+ )
19
+
20
+ # require 'logger'
21
+ # ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
22
+
23
+ require File.expand_path('../models.rb', __FILE__)
24
+
25
+ require File.expand_path('../shared_examples.rb', __FILE__)
@@ -0,0 +1,220 @@
1
+ describe MiniRecord do
2
+ before do
3
+ ActiveRecord::Base.descendants.each do |active_record|
4
+ ActiveRecord::Base.connection.drop_table active_record.table_name if active_record.table_exists?
5
+ end
6
+ end
7
+
8
+ it 'has #schema inside model' do
9
+ # For unknown reason separate specs doesn't works
10
+ ActiveRecord::Base.connection.table_exists?(Person.table_name).must_equal false
11
+ Person.auto_upgrade!
12
+ Person.table_name.must_equal 'people'
13
+ Person.db_columns.sort.must_equal %w[id name]
14
+ Person.column_names.sort.must_equal Person.db_columns
15
+ Person.column_names.sort.must_equal Person.schema_columns
16
+ person = Person.create(:name => 'foo')
17
+ person.name.must_equal 'foo'
18
+ proc { person.surname }.must_raise NoMethodError
19
+
20
+ # Add a column without lost data
21
+ Person.class_eval do
22
+ schema do |p|
23
+ p.string :name
24
+ p.string :surname
25
+ end
26
+ end
27
+ Person.auto_upgrade!
28
+ Person.count.must_equal 1
29
+ person = Person.last
30
+ person.name.must_equal 'foo'
31
+ person.surname.must_be_nil
32
+ person.update_attribute(:surname, 'bar')
33
+ Person.db_columns.sort.must_equal %w[id name surname]
34
+ Person.column_names.sort.must_equal Person.db_columns
35
+
36
+ # Remove a column without lost data
37
+ Person.class_eval do
38
+ schema do |p|
39
+ p.string :name
40
+ end
41
+ end
42
+ Person.auto_upgrade!
43
+ person = Person.last
44
+ person.name.must_equal 'foo'
45
+ proc { person.surname }.must_raise NoMethodError
46
+ Person.db_columns.sort.must_equal %w[id name]
47
+ Person.column_names.sort.must_equal Person.db_columns
48
+ Person.column_names.sort.must_equal Person.schema_columns
49
+
50
+ # Change column without lost data
51
+ Person.class_eval do
52
+ schema do |p|
53
+ p.text :name
54
+ end
55
+ end
56
+ person = Person.last
57
+ person.name.must_equal 'foo'
58
+ end
59
+
60
+ it 'has #key,col,property,attribute inside model' do
61
+ ActiveRecord::Base.connection.table_exists?(Post.table_name).must_equal false
62
+ ActiveRecord::Base.connection.table_exists?(Category.table_name).must_equal false
63
+ Post.auto_upgrade!; Category.auto_upgrade!
64
+ Post.column_names.sort.must_equal Post.db_columns
65
+ Category.column_names.sort.must_equal Category.schema_columns
66
+
67
+ # Check default properties
68
+ category = Category.create(:title => 'category')
69
+ post = Post.create(:title => 'foo', :body => 'bar', :category_id => category.id)
70
+ post = Post.first
71
+ post.title.must_equal 'foo'
72
+ post.body.must_equal 'bar'
73
+ post.category.must_equal category
74
+
75
+
76
+ # Remove a column
77
+ Post.reset_table_definition!
78
+ Post.class_eval do
79
+ col :name
80
+ col :category, :as => :references
81
+ end
82
+ Post.auto_upgrade!
83
+ post = Post.first
84
+ post.name.must_be_nil
85
+ post.category.must_equal category
86
+ post.wont_respond_to :title
87
+ end
88
+
89
+ it 'has indexes inside model' do
90
+ # Check indexes
91
+ Animal.auto_upgrade!
92
+ Animal.db_indexes.size.must_be :>, 0
93
+ Animal.db_indexes.must_equal Animal.indexes.keys.sort
94
+
95
+ indexes_was = Animal.db_indexes
96
+
97
+ # Remove an index
98
+ Animal.indexes.delete(indexes_was.pop)
99
+ Animal.auto_upgrade!
100
+ Animal.indexes.keys.sort.must_equal indexes_was
101
+ Animal.db_indexes.must_equal indexes_was
102
+
103
+ # Add a new index
104
+ Animal.class_eval do
105
+ col :category, :as => :references, :index => true
106
+ end
107
+ Animal.auto_upgrade!
108
+ Animal.db_columns.must_include "category_id"
109
+ Animal.db_indexes.must_equal((indexes_was << "index_animals_on_category_id").sort)
110
+ end
111
+
112
+ it 'works with STI' do
113
+ Pet.auto_upgrade!
114
+ Pet.reset_column_information
115
+ Pet.db_columns.must_include "type"
116
+ Dog.auto_upgrade!
117
+ Pet.db_columns.must_include "type"
118
+
119
+ # Now, let's we know if STI is working
120
+ Pet.create(:name => "foo")
121
+ Dog.create(:name => "bar")
122
+ Dog.count.must_equal 1
123
+ Dog.first.name.must_equal "bar"
124
+ Pet.count.must_equal 2
125
+ Pet.all.map(&:name).must_equal ["foo", "bar"]
126
+
127
+ # Check that this doesn't break things
128
+ Cat.auto_upgrade!
129
+ Dog.first.name.must_equal "bar"
130
+
131
+ # What's happen if we change schema?
132
+ Dog.table_definition.must_equal Pet.table_definition
133
+ Dog.indexes.must_equal Pet.indexes
134
+ Dog.class_eval do
135
+ col :bau
136
+ end
137
+ Dog.auto_upgrade!
138
+ Pet.db_columns.must_include "bau"
139
+ Dog.new.must_respond_to :bau
140
+ Cat.new.must_respond_to :bau
141
+ end
142
+
143
+ it 'works with custom inheritance column' do
144
+ User.auto_upgrade!
145
+ Administrator.create(:name => "Davide", :surname => "D'Agostino")
146
+ Customer.create(:name => "Foo", :surname => "Bar")
147
+ Administrator.count.must_equal 1
148
+ Administrator.first.name.must_equal "Davide"
149
+ Customer.count.must_equal 1
150
+ Customer.first.name.must_equal "Foo"
151
+ User.count.must_equal 2
152
+ User.first.role.must_equal "Administrator"
153
+ User.last.role.must_equal "Customer"
154
+ end
155
+
156
+ it 'allow multiple columns definitions' do
157
+ Fake.auto_upgrade!
158
+ Fake.create(:name => 'foo', :surname => 'bar', :category_id => 1, :group_id => 2)
159
+ fake = Fake.first
160
+ fake.name.must_equal 'foo'
161
+ fake.surname.must_equal 'bar'
162
+ fake.category_id.must_equal 1
163
+ fake.group_id.must_equal 2
164
+ end
165
+
166
+ it 'allows non-integer primary keys' do
167
+ Vegetable.auto_upgrade!
168
+ Vegetable.primary_key.must_equal 'latin_name'
169
+ end
170
+
171
+ it 'properly creates primary key columns so that ActiveRecord uses them' do
172
+ Vegetable.auto_upgrade!
173
+ Vegetable.delete_all
174
+ n = 'roobus roobious'
175
+ v = Vegetable.new; v.latin_name = n; v.save!
176
+ Vegetable.find(n).must_equal v
177
+ end
178
+
179
+ it 'automatically shortens long index names' do
180
+ AutomobileMakeModelYearVariant.auto_upgrade!
181
+ AutomobileMakeModelYearVariant.db_indexes.first.start_with?('index_automobile_make_model_ye').must_equal true
182
+ end
183
+
184
+ it 'properly creates primary key columns that are unique' do
185
+ Vegetable.auto_upgrade!
186
+ Vegetable.delete_all
187
+ n = 'roobus roobious'
188
+ v = Vegetable.new; v.latin_name = n; v.save!
189
+ if sqlite?
190
+ flunk # segfaults
191
+ # lambda { v = Vegetable.new; v.latin_name = n; v.save! }.must_raise(SQLite3::ConstraintException)
192
+ else
193
+ lambda { v = Vegetable.new; v.latin_name = n; v.save! }.must_raise(ActiveRecord::RecordNotUnique)
194
+ end
195
+ end
196
+
197
+ it 'is idempotent' do
198
+ ActiveRecord::Base.descendants.each do |active_record|
199
+ active_record.auto_upgrade!
200
+ active_record.reset_column_information
201
+ before = [ active_record.db_columns, active_record.db_indexes ]
202
+ active_record.auto_upgrade!
203
+ active_record.reset_column_information
204
+ [ active_record.db_columns, active_record.db_indexes ].must_equal before
205
+ active_record.auto_upgrade!
206
+ active_record.reset_column_information
207
+ active_record.auto_upgrade!
208
+ active_record.reset_column_information
209
+ active_record.auto_upgrade!
210
+ active_record.reset_column_information
211
+ [ active_record.db_columns, active_record.db_indexes ].must_equal before
212
+ end
213
+ end
214
+
215
+ private
216
+
217
+ def sqlite?
218
+ ActiveRecord::Base.connection.adapter_name =~ /sqlite/i
219
+ end
220
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'bundler/setup'
3
+ require 'mini_record'
4
+ require 'minitest/autorun'
5
+
6
+ module SpecHelper
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ def db_columns
13
+ connection.columns(table_name).map(&:name).sort
14
+ end
15
+
16
+ def db_indexes
17
+ connection.indexes(table_name).map(&:name).sort
18
+ end
19
+
20
+ def schema_columns
21
+ table_definition.columns.map { |c| c.name.to_s }.sort
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path('../spec_helper.rb', __FILE__)
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
+
5
+ # require 'logger'
6
+ # ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
7
+
8
+ require File.expand_path('../models.rb', __FILE__)
9
+
10
+ require File.expand_path('../shared_examples.rb', __FILE__)
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mini_record-compat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Davide D'Agostino
9
+ - Seamus Abshere
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-09-26 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: &2154134460 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2154134460
26
+ description: ! "\nWith it you can add the ability to create columns outside the default
27
+ schema, directly\nin your model in a similar way that you just know in others projects\nlike
28
+ \ DataMapper or MongoMapper.\n "
29
+ email:
30
+ - d.dagostino@lipsiasoft.com
31
+ - seamus@abshere.net
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - .gitignore
37
+ - .travis.yml
38
+ - Gemfile
39
+ - README.md
40
+ - Rakefile
41
+ - lib/mini_record.rb
42
+ - lib/mini_record/auto_schema.rb
43
+ - lib/mini_record/version.rb
44
+ - mini_record.gemspec
45
+ - spec/models.rb
46
+ - spec/mysql_spec.rb
47
+ - spec/postgresql_spec.rb
48
+ - spec/shared_examples.rb
49
+ - spec/spec_helper.rb
50
+ - spec/sqlite3_spec.rb
51
+ homepage: https://github.com/seamusabshere/mini_record
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.6
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Alternate gem published by Seamus Abshere for ActiveRecord 3.0 support. MiniRecord
75
+ is a micro gem that allow you to write schema inside your model as you can do in
76
+ DataMapper.
77
+ test_files:
78
+ - spec/models.rb
79
+ - spec/mysql_spec.rb
80
+ - spec/postgresql_spec.rb
81
+ - spec/shared_examples.rb
82
+ - spec/spec_helper.rb
83
+ - spec/sqlite3_spec.rb