active_record_inline_schema 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
@@ -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
+ - seamus@abshere.net
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake"
4
+ gem "minitest"
5
+ gem "sqlite3"
6
+ gem 'mysql'
7
+ gem 'sqlite3-ruby'
8
+ gem 'pg'
9
+
10
+ gemspec
@@ -0,0 +1,65 @@
1
+ # ActiveRecordInlineSchema
2
+
3
+ Define table structure (columns and indexes) inside your ActiveRecord models like you can do in migrations. Also similar to DataMapper inline schema syntax.
4
+
5
+ Specify columns like you would with ActiveRecord migrations and then run .auto_upgrade! Based on the mini_record gem from Davide D'Agostino, it adds fewer aliases, doesn't create timestamps and relationship columns automatically.
6
+
7
+ ## Production use
8
+
9
+ Over 2 years in [Brighter Planet's environmental impact API](http://impact.brighterplanet.com) and [reference data service](http://data.brighterplanet.com).
10
+
11
+ Lots and lots of use in the [`earth` library](https://github.com/brighterplanet/earth).
12
+
13
+ ## Examples
14
+
15
+ class Breed < ActiveRecord::Base
16
+ col :species_name
17
+ col :weight, :type => :float
18
+ col :weight_units
19
+ end
20
+ Breed.auto_upgrade!
21
+
22
+ class Airport < ActiveRecord::Base
23
+ self.primary_key = "iata_code"
24
+ belongs_to :country, :foreign_key => 'country_iso_3166_code', :primary_key => 'iso_3166_code'
25
+ col :iata_code
26
+ col :name
27
+ col :city
28
+ col :country_name
29
+ col :country_iso_3166_code
30
+ col :latitude, :type => :float
31
+ col :longitude, :type => :float
32
+ end
33
+ Airport.auto_upgrade!
34
+
35
+ class ApiResponse < ActiveRecord::Base
36
+ col :raw_body, :type => 'varbinary(16384)'
37
+ end
38
+ ApiResponse.auto_upgrade!
39
+
40
+ ## Credits
41
+
42
+ Massive thanks to DAddYE, who you follow on twitter [@daddye](http://twitter.com/daddye) and look at his site at [daddye.it](http://www.daddye.it)
43
+
44
+ ## TODO
45
+
46
+ * merge back into mini_record? they make some choices (automatically creating relationships, mixing in lots of aliases, etc.) that I don't need
47
+ * make the documentation as good as mini_record
48
+
49
+ ## Copyright
50
+
51
+ Copyright 2012 Seamus Abshere
52
+
53
+ Adapted from [mini_record](https://github.com/DAddYE/mini_record), which is copyright 2011 Davide D'Agostino
54
+
55
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
56
+ associated documentation files (the “Software”), to deal in the Software without restriction, including without
57
+ limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
58
+ and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
63
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
64
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'spec'
7
+ test.test_files = Dir['spec/**/*_spec.rb']
8
+ test.verbose = true
9
+ end
10
+
11
+ task :test_each_db_adapter do
12
+ %w{ mysql sqlite3 postgresql }.each do |db_adapter|
13
+ puts
14
+ puts "#{'*'*10} Running #{db_adapter} tests"
15
+ puts
16
+ puts `bundle exec rake test DB_ADAPTER=#{db_adapter} TEST=spec/#{db_adapter}_spec.rb`
17
+ end
18
+ end
19
+
20
+ task :default => :test_each_db_adapter
21
+ task :spec => :test_each_db_adapter
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "active_record_inline_schema/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "active_record_inline_schema"
7
+ s.version = ActiveRecordInlineSchema::VERSION
8
+ s.authors = ["Seamus Abshere", "Davide D'Agostino"]
9
+ s.email = ["seamus@abshere.net", "d.dagostino@lipsiasoft.com"]
10
+ s.homepage = "https://github.com/seamusabshere/active_record_inline_schema"
11
+ s.summary = %q{Define table structure (columns and indexes) inside your ActiveRecord models like you can do in migrations. Also similar to DataMapper inline schema syntax.}
12
+ s.description = %q{Specify columns like you would with ActiveRecord migrations and then run .auto_upgrade! Based on the mini_record gem from Davide D'Agostino, it adds fewer aliases, doesn't create timestamps and relationship columns automatically.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency "activerecord", ">=3"
20
+ # s.add_runtime_dependency "activerecord", "3.0.12"
21
+ # s.add_runtime_dependency "activerecord", "3.1.3"
22
+ # s.add_runtime_dependency "activerecord", "3.2.2"
23
+
24
+ # dev dependencies appear to be in the Gemfile
25
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'active_record'
3
+ require 'active_record_inline_schema/auto_schema'
4
+
5
+ ActiveRecord::Base.send(:include, ActiveRecordInlineSchema::AutoSchema)
@@ -0,0 +1,229 @@
1
+ require 'zlib'
2
+ module ActiveRecordInlineSchema
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
+ if table_definition.respond_to?(type)
31
+ table_definition.send(type, column_name, options)
32
+ else
33
+ table_definition.column(column_name, type, options)
34
+ end
35
+ column_name = table_definition.columns[-1].name
36
+ case index_name = options.delete(:index)
37
+ when Hash
38
+ add_index(options.delete(:column) || column_name, index_name)
39
+ when TrueClass
40
+ add_index(column_name)
41
+ when String, Symbol, Array
42
+ add_index(index_name)
43
+ end
44
+ end
45
+ end
46
+
47
+ def reset_table_definition!
48
+ @_table_definition = nil
49
+ end
50
+
51
+ def schema
52
+ reset_table_definition!
53
+ yield table_definition
54
+ table_definition
55
+ end
56
+
57
+ def add_index(column_name, options={})
58
+ index_name = shorten_index_name connection.index_name(table_name, :column => column_name)
59
+ indexes[index_name] = options.merge(:column => column_name, :name => index_name)
60
+ index_name
61
+ end
62
+
63
+ def connection?
64
+ !!connection
65
+ rescue Exception => e
66
+ puts "\e[31m%s\e[0m" % e.message.strip
67
+ false
68
+ end
69
+
70
+ def shorten_index_name(name)
71
+ if name.length < connection.index_name_length
72
+ name
73
+ else
74
+ name[0..(connection.index_name_length-11)] + ::Zlib.crc32(name).to_s
75
+ end
76
+ end
77
+
78
+ def sqlite?
79
+ connection.adapter_name =~ /sqlite/i
80
+ end
81
+
82
+ def mysql?
83
+ connection.adapter_name =~ /mysql/i
84
+ end
85
+
86
+ def postgresql?
87
+ connection.adapter_name =~ /postgresql/i
88
+ end
89
+
90
+ def auto_upgrade!(create_table_options = '')
91
+ return unless connection?
92
+
93
+ # normally activerecord's mysql adapter does this
94
+ if mysql?
95
+ create_table_options ||= 'ENGINE=InnoDB'
96
+ end
97
+
98
+ non_standard_primary_key = if (primary_key_column = table_definition.columns.detect { |column| column.name.to_s == primary_key.to_s })
99
+ primary_key_column.type != :primary_key
100
+ end
101
+
102
+ unless non_standard_primary_key
103
+ table_definition.column :id, :primary_key
104
+ end
105
+
106
+ # Table doesn't exist, create it
107
+ unless connection.table_exists? table_name
108
+
109
+ # avoid using connection.create_table because in 3.0.x it ignores table_definition
110
+ # and it also is too eager about adding a primary key column
111
+ create_sql = "CREATE TABLE #{quoted_table_name} (#{table_definition.to_sql}) #{create_table_options}"
112
+
113
+ if sqlite?
114
+ connection.execute create_sql
115
+ if non_standard_primary_key
116
+ add_index primary_key, :unique => true
117
+ end
118
+ elsif postgresql?
119
+ connection.execute create_sql
120
+ if non_standard_primary_key
121
+ # can't use add_index method because it won't let you do "PRIMARY KEY"
122
+ connection.execute "ALTER TABLE #{quoted_table_name} ADD PRIMARY KEY (#{quoted_primary_key})"
123
+ end
124
+ elsif mysql?
125
+ if non_standard_primary_key
126
+ # only string keys are supported
127
+ create_sql.sub! %r{#{connection.quote_column_name(primary_key)} varchar\(255\)([^,\)]*)}, "#{connection.quote_column_name(primary_key)} varchar(255)\\1 PRIMARY KEY"
128
+ create_sql.sub! 'DEFAULT NULLPRIMARY KEY', 'PRIMARY KEY'
129
+ end
130
+ connection.execute create_sql
131
+ end
132
+
133
+ if connection.respond_to?(:schema_cache)
134
+ connection.schema_cache.clear!
135
+ end
136
+ reset_column_information
137
+ end
138
+
139
+ # Add to schema inheritance column if necessary
140
+ if descendants.present? && !table_definition.columns.any? { |column| column.name.to_s == inheritance_column.to_s }
141
+ table_definition.column inheritance_column, :string
142
+ end
143
+
144
+ # Grab database columns
145
+ fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
146
+ hash[column.name] = column
147
+ hash
148
+ end
149
+
150
+ # Grab new schema
151
+ fields_in_schema = table_definition.columns.inject({}) do |hash, column|
152
+ hash[column.name.to_s] = column
153
+ hash
154
+ end
155
+
156
+ # Remove fields from db no longer in schema
157
+ (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
158
+ column = fields_in_db[field]
159
+ connection.remove_column table_name, column.name
160
+ end
161
+
162
+ # Add fields to db new to schema
163
+ (fields_in_schema.keys - fields_in_db.keys).each do |field|
164
+ column = fields_in_schema[field]
165
+ options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
166
+ options[:default] = column.default if !column.default.nil?
167
+ options[:null] = column.null if !column.null.nil?
168
+ connection.add_column table_name, column.name, column.type.to_sym, options
169
+ end
170
+
171
+ # Change attributes of existent columns
172
+ (fields_in_schema.keys & fields_in_db.keys).each do |field|
173
+ if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
174
+ changed = false # flag
175
+ new_type = fields_in_schema[field].type.to_sym
176
+ new_attr = {}
177
+
178
+ # First, check if the field type changed
179
+ if (fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym) and (fields_in_schema[field].type.to_sym != fields_in_db[field].sql_type.to_sym)
180
+ # $stderr.puts "A(#{field}) - #{fields_in_schema[field].type.to_sym}"
181
+ # $stderr.puts "B(#{field}) - #{fields_in_db[field].type.to_sym} - #{fields_in_db[field].sql_type.to_sym}"
182
+ changed = true
183
+ end
184
+
185
+ # Special catch for precision/scale, since *both* must be specified together
186
+ # Always include them in the attr struct, but they'll only get applied if changed = true
187
+ new_attr[:precision] = fields_in_schema[field][:precision]
188
+ new_attr[:scale] = fields_in_schema[field][:scale]
189
+
190
+ # Next, iterate through our extended attributes, looking for any differences
191
+ # This catches stuff like :null, :precision, etc
192
+ fields_in_schema[field].each_pair do |att,value|
193
+ next if att == :type or att == :base or att == :name # special cases
194
+ if !value.nil? && value != fields_in_db[field].send(att)
195
+ # $stderr.puts "C(#{att}) - #{value.inspect}"
196
+ # $stderr.puts "D(#{att}) - #{fields_in_db[field].send(att).inspect}"
197
+ new_attr[att] = value
198
+ changed = true
199
+ end
200
+ end
201
+
202
+ # Change the column if applicable
203
+ connection.change_column table_name, field, new_type, new_attr if changed
204
+ end
205
+ end
206
+
207
+ # Remove old index
208
+ indexes_in_db = connection.indexes(table_name).map(&:name)
209
+ (indexes_in_db - indexes.keys).each do |name|
210
+ connection.remove_index(table_name, :name => name)
211
+ end
212
+
213
+ # Add indexes
214
+ indexes.each do |name, options|
215
+ options = options.dup
216
+ unless connection.indexes(table_name).detect { |i| i.name == name }
217
+ connection.add_index(table_name, options.delete(:column), options)
218
+ end
219
+ end
220
+
221
+ # Reload column information
222
+ if connection.respond_to?(:schema_cache)
223
+ connection.schema_cache.clear!
224
+ end
225
+ reset_column_information
226
+ end
227
+ end # ClassMethods
228
+ end # AutoSchema
229
+ end # ActiveRecordInlineSchema
@@ -0,0 +1,3 @@
1
+ module ActiveRecordInlineSchema
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,93 @@
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
+ col :title
14
+ col :body
15
+ col :category, :as => :references
16
+ belongs_to :category
17
+ end
18
+
19
+ class Category < ActiveRecord::Base
20
+ include SpecHelper
21
+
22
+ col :title
23
+ has_many :posts
24
+ end
25
+
26
+ class Animal < ActiveRecord::Base
27
+ include SpecHelper
28
+
29
+ col :name, :index => true
30
+ add_index :id
31
+ end
32
+
33
+ class Pet < ActiveRecord::Base
34
+ include SpecHelper
35
+
36
+ col :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
+ self.primary_key = 'latin_name'
45
+
46
+ col :latin_name
47
+ col :common_name
48
+ end
49
+
50
+ class Gender < ActiveRecord::Base
51
+ include SpecHelper
52
+
53
+ self.primary_key = 'name'
54
+
55
+ col :name
56
+ end
57
+
58
+ class User < ActiveRecord::Base
59
+ include SpecHelper
60
+ self.inheritance_column = 'role' # messed up in 3.2.2
61
+ col :name
62
+ col :surname
63
+ col :role
64
+ end
65
+ class Administrator < User; end
66
+ class Customer < User; end
67
+
68
+ class Fake < ActiveRecord::Base
69
+ include SpecHelper
70
+ col :name, :surname
71
+ col :category, :group, :as => :references
72
+ end
73
+
74
+ class AutomobileMakeModelYearVariant < ActiveRecord::Base
75
+ include SpecHelper
76
+ col :make_model_year_name
77
+ add_index :make_model_year_name
78
+ end
79
+
80
+ case ENV['DB_ADAPTER']
81
+ when 'mysql'
82
+ class CustomMysql < ActiveRecord::Base
83
+ include SpecHelper
84
+ col :varb, :type => 'varbinary(255)'
85
+ col :varc, :type => 'varchar(255)'
86
+ end
87
+ when 'postgresql'
88
+ class CustomPostgresql < ActiveRecord::Base
89
+ include SpecHelper
90
+ col :inet, :type => 'inet'
91
+ col :bytea, :type => 'bytea'
92
+ end
93
+ 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_active_record_inline_schema'
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_active_record_inline_schema'
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,240 @@
1
+ describe ActiveRecordInlineSchema do
2
+ before do
3
+ ActiveRecord::Base.descendants.each do |active_record|
4
+ ActiveRecord::Base.connection.drop_table active_record.table_name rescue nil
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 'properly creates tables with one column, a string primary key' do
198
+ Gender.auto_upgrade!
199
+ Gender.column_names.must_equal ['name']
200
+ end
201
+
202
+ it 'is idempotent' do
203
+ ActiveRecord::Base.descendants.each do |active_record|
204
+ active_record.auto_upgrade!
205
+ active_record.reset_column_information
206
+ before = [ active_record.db_columns, active_record.db_indexes ]
207
+ active_record.auto_upgrade!
208
+ active_record.reset_column_information
209
+ [ active_record.db_columns, active_record.db_indexes ].must_equal before
210
+ active_record.auto_upgrade!
211
+ active_record.reset_column_information
212
+ active_record.auto_upgrade!
213
+ active_record.reset_column_information
214
+ active_record.auto_upgrade!
215
+ active_record.reset_column_information
216
+ [ active_record.db_columns, active_record.db_indexes ].must_equal before
217
+ end
218
+ end
219
+
220
+ case ENV['DB_ADAPTER']
221
+ when 'mysql'
222
+ it "takes custom types" do
223
+ CustomMysql.auto_upgrade!
224
+ CustomMysql.columns_hash['varb'].sql_type.must_equal 'varbinary(255)'
225
+ CustomMysql.columns_hash['varc'].sql_type.must_equal 'varchar(255)'
226
+ end
227
+ when 'postgresql'
228
+ it "takes custom types" do
229
+ CustomPostgresql.auto_upgrade!
230
+ CustomPostgresql.columns_hash['inet'].sql_type.must_equal 'inet'
231
+ CustomPostgresql.columns_hash['bytea'].sql_type.must_equal 'bytea'
232
+ end
233
+ end
234
+
235
+ private
236
+
237
+ def sqlite?
238
+ ActiveRecord::Base.connection.adapter_name =~ /sqlite/i
239
+ end
240
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'bundler/setup'
3
+ require 'active_record_inline_schema'
4
+ require 'minitest/autorun'
5
+
6
+ # require 'logger'
7
+ # ActiveRecord::Base.logger = Logger.new($stderr)
8
+ # ActiveRecord::Base.logger.level = Logger::DEBUG
9
+
10
+ module SpecHelper
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ def db_columns
17
+ connection.columns(table_name).map(&:name).sort
18
+ end
19
+
20
+ def db_indexes
21
+ connection.indexes(table_name).map(&:name).sort
22
+ end
23
+
24
+ def schema_columns
25
+ table_definition.columns.map { |c| c.name.to_s }.sort
26
+ end
27
+ end
28
+ 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: active_record_inline_schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Seamus Abshere
9
+ - Davide D'Agostino
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-03-26 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: &2166623420 !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: *2166623420
26
+ description: Specify columns like you would with ActiveRecord migrations and then
27
+ run .auto_upgrade! Based on the mini_record gem from Davide D'Agostino, it adds
28
+ fewer aliases, doesn't create timestamps and relationship columns automatically.
29
+ email:
30
+ - seamus@abshere.net
31
+ - d.dagostino@lipsiasoft.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - .gitignore
37
+ - .travis.yml
38
+ - Gemfile
39
+ - README.md
40
+ - Rakefile
41
+ - active_record_inline_schema.gemspec
42
+ - lib/active_record_inline_schema.rb
43
+ - lib/active_record_inline_schema/auto_schema.rb
44
+ - lib/active_record_inline_schema/version.rb
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/active_record_inline_schema
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.15
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Define table structure (columns and indexes) inside your ActiveRecord models
75
+ like you can do in migrations. Also similar to DataMapper inline schema syntax.
76
+ test_files:
77
+ - spec/models.rb
78
+ - spec/mysql_spec.rb
79
+ - spec/postgresql_spec.rb
80
+ - spec/shared_examples.rb
81
+ - spec/spec_helper.rb
82
+ - spec/sqlite3_spec.rb
83
+ has_rdoc: