active_record_inline_schema 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: