active_record_inline_schema 0.4.0 → 0.5.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.
data/CHANGELOG ADDED
@@ -0,0 +1,53 @@
1
+ ## 0.5.0 / 2012-04-19
2
+
3
+ * Known issues
4
+
5
+ * Custom inheritance columns will be ignored unless you have activerecord >3.2.3 (unreleased as of this writing). The bug is https://github.com/rails/rails/pull/5327
6
+
7
+ * Breaking changes
8
+
9
+ * the block form (schema {}) is removed
10
+ * .col only accepts one column at a time
11
+ * .col no longer accepts :as => :references
12
+ was: col :group, :as => :references
13
+ now: col :group_id, :type => :integer
14
+ * .col no longer accepts :index => true
15
+ was: col :foo, :index => true
16
+ now: col :foo and then add_index :foo
17
+ * A whole bunch of class methods are no longer mixed into ActiveRecord::Base (see Enhancements below)
18
+
19
+ * Enhancements
20
+
21
+ * Database connection no longer needs to be established before you define columns/indexes (but of course you have to connect before you auto_upgrade!)
22
+ * Only adds four (4) class methods to ActiveRecord::Base: .auto_upgrade!, .col, .add_index, and .inline_schema_config
23
+ * Tested on MRI 1.8, MRI 1.9, and JRuby 1.6.7+ across mysql, postgresql, and sqlite3
24
+ * Simply and DRY via a big refactor
25
+
26
+ ## Difference between mini_record v0.2.1 and active_record_inline_schema v0.4.0
27
+
28
+ Having diverged at [this commit](https://github.com/DAddYE/mini_record/commit/55b2545f9772f7500d3782ac530b3da456f50023), I did stuff like...
29
+
30
+ * didn't set primary key on table definition
31
+ * allow custom column types
32
+ * removed aliases
33
+ * shorten name with zlib trick
34
+ * allow passing create_table_options
35
+ * default to ENGINE=InnoDB on mysql
36
+ * detect and create table for non-standard primary key
37
+ * move where schema inheritance columns are defined
38
+ * removed table_definition accessor
39
+ * allow custom column types like `varbinary`
40
+ * more compatible with ActiveRecord 3.0 and 3.2.2
41
+
42
+ ## Difference between mini_record v0.2.1 and mini_record v0.3.0 (which I didn't fork)
43
+
44
+ After I made this list, I decided to diverge from mini_record v0.2.1 instead of doing a pull request:
45
+
46
+ * allow custom column types
47
+ * add timestamps
48
+ * remove unused (unmentioned) tables
49
+ * call auto_upgrade on descendant tables
50
+ * automagically generate fields from associations
51
+ * revised difference checking code
52
+
53
+ Pity the forker (me)!
data/Gemfile CHANGED
@@ -1,10 +1,26 @@
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'
1
+ source :rubygems
9
2
 
10
3
  gemspec
4
+
5
+ gem 'rake'
6
+ gem 'minitest'
7
+ gem 'minitest-reporters'
8
+
9
+ platforms :ruby do
10
+ # if RUBY_VERSION >= '1.9'
11
+ # gem 'debugger'
12
+ # else
13
+ # gem 'ruby-debug19'
14
+ # end
15
+ # gem 'mysql2', '~>0.2'
16
+ gem 'mysql2', '>=0.3'
17
+ gem 'sqlite3'
18
+ gem 'pg'
19
+ end
20
+
21
+ platforms :jruby do
22
+ gem 'jruby-openssl'
23
+ gem 'activerecord-jdbcsqlite3-adapter'
24
+ gem 'activerecord-jdbcmysql-adapter'
25
+ gem 'activerecord-jdbcpostgresql-adapter'
26
+ end
data/README.md CHANGED
@@ -33,6 +33,9 @@ Lots and lots of use in the [`earth` library](https://github.com/brighterplanet/
33
33
  Airport.auto_upgrade!
34
34
 
35
35
  class ApiResponse < ActiveRecord::Base
36
+ # store with: self.body = Zlib::Deflate.deflate(body, Zlib::BEST_SPEED)
37
+ # retrieve with: Zlib::Inflate.inflate(raw_body).force_encoding 'UTF-8'
38
+ # just an idea!
36
39
  col :raw_body, :type => 'varbinary(16384)'
37
40
  end
38
41
  ApiResponse.auto_upgrade!
@@ -43,8 +46,12 @@ Massive thanks to DAddYE, who you follow on twitter [@daddye](http://twitter.com
43
46
 
44
47
  ## TODO
45
48
 
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
49
  * make the documentation as good as mini_record
50
+ * investigate switching back to ActiveRecord::ConnectionAdapters::TableDefinition as a way of holding column and index info
51
+
52
+ ## History
53
+
54
+ Forked from [`mini_record` version v0.2.1](https://github.com/DAddYE/mini_record) - thanks @daddye! See CHANGELOG for a rough outline of the differences.
48
55
 
49
56
  ## Copyright
50
57
 
@@ -1,6 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "active_record_inline_schema/version"
2
+ require File.expand_path("../lib/active_record_inline_schema/version", __FILE__)
4
3
 
5
4
  Gem::Specification.new do |s|
6
5
  s.name = "active_record_inline_schema"
@@ -16,10 +15,12 @@ Gem::Specification.new do |s|
16
15
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
16
  s.require_paths = ["lib"]
18
17
 
18
+ s.add_runtime_dependency 'lock_method'
19
+ s.add_runtime_dependency 'activesupport'
19
20
  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"
21
+ # s.add_runtime_dependency "activerecord", "~>3.0" # must use mysql2 ~>0.2 to test
22
+ # s.add_runtime_dependency "activerecord", "~>3.1"
23
+ # s.add_runtime_dependency "activerecord", "~>3.2"
23
24
 
24
25
  # dev dependencies appear to be in the Gemfile
25
26
  end
@@ -0,0 +1,27 @@
1
+ require 'thread'
2
+
3
+ module ActiveRecordInlineSchema::ActiveRecordClassMethods
4
+ MUTEX = ::Mutex.new
5
+
6
+ def inline_schema_config
7
+ if superclass != ::ActiveRecord::Base
8
+ return base_class.inline_schema_config
9
+ end
10
+ @inline_schema_config || MUTEX.synchronize do
11
+ @inline_schema_config ||= ::ActiveRecordInlineSchema::Config.new self
12
+ end
13
+ end
14
+
15
+ def col(column_name, options = {})
16
+ inline_schema_config.add_ideal_column column_name, options
17
+ end
18
+
19
+ # this is not a typo - specify column name, not index name
20
+ def add_index(column_name, options = {})
21
+ inline_schema_config.add_ideal_index column_name, options
22
+ end
23
+
24
+ def auto_upgrade!(create_table_options = nil)
25
+ inline_schema_config.apply create_table_options
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ class ActiveRecordInlineSchema::Config::Column
2
+ DEFAULT_TYPE = :string
3
+
4
+ attr_reader :parent
5
+ attr_reader :name
6
+ attr_reader :type
7
+ attr_reader :options
8
+
9
+ def initialize(parent, name, options)
10
+ @parent = parent
11
+ @name = name.to_s
12
+ options = options.symbolize_keys
13
+ if options.slice(:precision, :scale).keys.length == 1
14
+ raise ::ArgumentError, %{[active_record_inline_schema] :precision and :scale must always be specified together}
15
+ end
16
+ @type = options.fetch(:type, DEFAULT_TYPE).to_sym
17
+ @options = options.except :type, :name
18
+ end
19
+
20
+ def inject(table_definition)
21
+ if type != :primary_key and table_definition.respond_to?(type)
22
+ table_definition.send type, name, options
23
+ else
24
+ table_definition.column name, type, options
25
+ end
26
+ end
27
+
28
+ def eql?(other)
29
+ other.is_a?(self.class) and parent == other.parent and name == other.name and options == other.options
30
+ end
31
+
32
+ def hash
33
+ [parent, name, options].hash
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ require 'zlib'
2
+ require 'thread'
3
+
4
+ class ActiveRecordInlineSchema::Config::Index
5
+ attr_reader :parent
6
+ attr_reader :column_name
7
+ attr_reader :initial_options
8
+
9
+ delegate :connection, :to => :parent
10
+ delegate :model, :to => :parent
11
+
12
+ def initialize(parent, column_name, options)
13
+ @parent = parent
14
+ @column_name = column_name.to_s
15
+ @initial_options = options.symbolize_keys
16
+ @name_mutex = ::Mutex.new
17
+ end
18
+
19
+ def name
20
+ @name || @name_mutex.synchronize do
21
+ @name ||= begin
22
+ max_name_length = connection.index_name_length
23
+ prototype = connection.index_name model.table_name, :column => column_name
24
+ if prototype.length < max_name_length
25
+ prototype
26
+ else
27
+ prototype[0..(max_name_length-11)] + ::Zlib.crc32(prototype).to_s
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def options
34
+ @initial_options.merge(:column_name => column_name, :name => name)
35
+ end
36
+
37
+ def eql?(other)
38
+ other.is_a?(self.class) and parent == other.parent and column_name == other.column_name and initial_options == other.initial_options
39
+ end
40
+
41
+ def hash
42
+ [parent, column_name, initial_options].hash
43
+ end
44
+ end
@@ -0,0 +1,219 @@
1
+ require 'set'
2
+ require 'lock_method'
3
+
4
+ class ActiveRecordInlineSchema::Config
5
+ DEFAULT_CREATE_TABLE_OPTIONS = {
6
+ :mysql => 'ENGINE=InnoDB'
7
+ }
8
+
9
+ attr_reader :model
10
+ attr_reader :ideal_columns
11
+ attr_reader :ideal_indexes
12
+
13
+ def initialize(model)
14
+ @model = model
15
+ @ideal_columns = ::Set.new
16
+ @ideal_indexes = ::Set.new
17
+ end
18
+
19
+ def add_ideal_column(column_name, options)
20
+ ideal_columns.add Column.new(self, column_name, options)
21
+ end
22
+
23
+ def add_ideal_index(column_name, options)
24
+ ideal_indexes.add Index.new(self, column_name, options)
25
+ end
26
+
27
+ def apply(create_table_options)
28
+ non_standard_primary_key = if (primary_key_column = find_ideal_column(model.primary_key))
29
+ primary_key_column.type != :primary_key
30
+ end
31
+
32
+ unless non_standard_primary_key
33
+ add_ideal_column :id, :type => :primary_key
34
+ end
35
+
36
+ # Table doesn't exist, create it
37
+ unless connection.table_exists? model.table_name
38
+
39
+ if mysql?
40
+ create_table_options ||= DEFAULT_CREATE_TABLE_OPTIONS[database_type]
41
+ end
42
+
43
+ table_definition = ::ActiveRecord::ConnectionAdapters::TableDefinition.new connection
44
+ ideal_columns.each do |ideal_column|
45
+ ideal_column.inject table_definition
46
+ end
47
+
48
+ # avoid using connection.create_table because in 3.0.x it ignores table_definition
49
+ # and it also is too eager about adding a primary key column
50
+ create_sql = "CREATE TABLE #{model.quoted_table_name} (#{table_definition.to_sql}) #{create_table_options}"
51
+
52
+ if sqlite?
53
+ connection.execute create_sql
54
+ if non_standard_primary_key
55
+ add_ideal_index model.primary_key, :unique => true
56
+ end
57
+ elsif postgresql?
58
+ connection.execute create_sql
59
+ if non_standard_primary_key
60
+ # can't use add_index method because it won't let you do "PRIMARY KEY"
61
+ connection.execute "ALTER TABLE #{model.quoted_table_name} ADD PRIMARY KEY (#{model.quoted_primary_key})"
62
+ end
63
+ elsif mysql?
64
+ if non_standard_primary_key
65
+ # only string keys are supported
66
+ create_sql.sub! %r{#{connection.quote_column_name(model.primary_key)} varchar\(255\)([^,\)]*)}, "#{connection.quote_column_name(model.primary_key)} varchar(255)\\1 PRIMARY KEY"
67
+ create_sql.sub! 'DEFAULT NULLPRIMARY KEY', 'PRIMARY KEY'
68
+ end
69
+ connection.execute create_sql
70
+ end
71
+ safe_reset_column_information
72
+ end
73
+
74
+ # Add to schema inheritance column if necessary
75
+ if model.descendants.any? and not find_ideal_column(model.inheritance_column)
76
+ add_ideal_column model.inheritance_column, :type => :string
77
+ end
78
+
79
+ # Remove fields from db no longer in schema
80
+ existing_column_names.reject do |existing_column_name|
81
+ find_ideal_column existing_column_name
82
+ end.each do |existing_column_name|
83
+ connection.remove_column model.table_name, existing_column_name
84
+ end
85
+
86
+ # Add fields to db new to schema
87
+ ideal_columns.reject do |ideal_column|
88
+ find_existing_column ideal_column.name
89
+ end.each do |ideal_column|
90
+ connection.add_column model.table_name, ideal_column.name, ideal_column.type, ideal_column.options
91
+ end
92
+
93
+ # Change attributes of existent columns
94
+ existing_columns_hash.each do |existing_column_name, existing_column|
95
+ next if existing_column_name.to_s == model.primary_key.to_s
96
+ ideal_column = find_ideal_column existing_column_name
97
+ option_changes = {}
98
+
99
+ # First, check if the field type changed
100
+ type_changed = !([existing_column.type.to_s, existing_column.sql_type.to_s].include?(ideal_column.type.to_s))
101
+
102
+ # Next, iterate through our extended attributes, looking for any differences
103
+ # This catches stuff like :null, :precision, etc
104
+ ideal_column.options.except(:base).each do |k, v|
105
+ if !v.nil? and v != existing_column.send(k)
106
+ option_changes[k] = v
107
+ end
108
+ end
109
+
110
+ # Change the column if applicable
111
+ if type_changed or option_changes.any?
112
+ connection.change_column model.table_name, existing_column_name, ideal_column.type, option_changes
113
+ end
114
+ end
115
+
116
+ # Remove old index
117
+ existing_index_names.reject do |existing_index_name|
118
+ find_ideal_index existing_index_name
119
+ end.each do |existing_index_name|
120
+ connection.remove_index model.table_name, :name => existing_index_name
121
+ end
122
+
123
+ # Add indexes
124
+ ideal_indexes.reject do |ideal_index|
125
+ find_existing_index ideal_index.name
126
+ end.each do |ideal_index|
127
+ connection.add_index model.table_name, ideal_index.column_name, ideal_index.options
128
+ end
129
+
130
+ safe_reset_column_information
131
+ end
132
+ lock_method :apply, :ttl => 60
133
+
134
+ def as_lock
135
+ if connection.respond_to?(:current_database)
136
+ [connection.current_database, model.name]
137
+ else
138
+ model.name
139
+ end
140
+ end
141
+
142
+ def clear
143
+ @ideal_columns = ::Set.new
144
+ @ideal_indexes = ::Set.new
145
+ end
146
+ lock_method :clear, :ttl => 60
147
+
148
+ private
149
+
150
+ def find_ideal_column(name)
151
+ ideal_columns.detect { |ideal_column| ideal_column.name.to_s == name.to_s }
152
+ end
153
+
154
+ def find_existing_column(name)
155
+ existing_column_names.detect { |existing_column_name| existing_column_name.to_s == name.to_s }
156
+ end
157
+
158
+ def find_ideal_index(name)
159
+ ideal_indexes.detect { |ideal_index| ideal_index.name.to_s == name.to_s }
160
+ end
161
+
162
+ def find_existing_index(name)
163
+ existing_index_names.detect { |existing_index_name| existing_index_name.to_s == name.to_s }
164
+ end
165
+
166
+ def safe_reset_column_information
167
+ if connection.respond_to?(:schema_cache)
168
+ connection.schema_cache.clear!
169
+ end
170
+ model.reset_column_information
171
+ model.descendants.each do |descendant|
172
+ descendant.reset_column_information
173
+ end
174
+ end
175
+
176
+ def existing_index_names
177
+ safe_reset_column_information
178
+ connection.indexes(model.table_name).map(&:name)
179
+ end
180
+
181
+ def existing_column_names
182
+ safe_reset_column_information
183
+ model.column_names
184
+ end
185
+
186
+ def existing_columns_hash
187
+ safe_reset_column_information
188
+ model.columns_hash
189
+ end
190
+
191
+ def connection
192
+ unless model.connection.active?
193
+ raise ::RuntimeError, %{[active_record_inline_schema] Must connect to database before running ActiveRecord::Base.auto_upgrade!}
194
+ end
195
+ model.connection
196
+ end
197
+
198
+ def database_type
199
+ if mysql?
200
+ :mysql
201
+ elsif postgresql?
202
+ :postgresql
203
+ elsif sqlite?
204
+ :sqlite
205
+ end
206
+ end
207
+
208
+ def sqlite?
209
+ connection.adapter_name =~ /sqlite/i
210
+ end
211
+
212
+ def mysql?
213
+ connection.adapter_name =~ /mysql/i
214
+ end
215
+
216
+ def postgresql?
217
+ connection.adapter_name =~ /postgresql/i
218
+ end
219
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordInlineSchema
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -1,5 +1,12 @@
1
- require 'rubygems' unless defined?(Gem)
1
+ require 'active_support/core_ext'
2
2
  require 'active_record'
3
- require 'active_record_inline_schema/auto_schema'
4
3
 
5
- ActiveRecord::Base.send(:include, ActiveRecordInlineSchema::AutoSchema)
4
+ require 'active_record_inline_schema/config'
5
+ require 'active_record_inline_schema/config/column'
6
+ require 'active_record_inline_schema/config/index'
7
+ require 'active_record_inline_schema/active_record_class_methods'
8
+
9
+ module ActiveRecordInlineSchema
10
+ end
11
+
12
+ ActiveRecord::Base.extend ActiveRecordInlineSchema::ActiveRecordClassMethods
data/spec/models.rb CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  class Person < ActiveRecord::Base
4
4
  include SpecHelper
5
- schema do |s|
6
- s.string :name
7
- end
5
+ col :name
8
6
  end
9
7
 
10
8
  class Post < ActiveRecord::Base
@@ -12,7 +10,7 @@ class Post < ActiveRecord::Base
12
10
 
13
11
  col :title
14
12
  col :body
15
- col :category, :as => :references
13
+ col :category_id, :type => :integer
16
14
  belongs_to :category
17
15
  end
18
16
 
@@ -26,14 +24,16 @@ end
26
24
  class Animal < ActiveRecord::Base
27
25
  include SpecHelper
28
26
 
29
- col :name, :index => true
27
+ col :name
28
+ add_index :name
30
29
  add_index :id
31
30
  end
32
31
 
33
32
  class Pet < ActiveRecord::Base
34
33
  include SpecHelper
35
34
 
36
- col :name, :index => true
35
+ col :name
36
+ add_index :name
37
37
  end
38
38
  class Dog < Pet; end
39
39
  class Cat < Pet; end
@@ -56,8 +56,8 @@ class Gender < ActiveRecord::Base
56
56
  end
57
57
 
58
58
  class User < ActiveRecord::Base
59
+ self.inheritance_column = 'role'
59
60
  include SpecHelper
60
- self.inheritance_column = 'role' # messed up in 3.2.2
61
61
  col :name
62
62
  col :surname
63
63
  col :role
@@ -67,8 +67,10 @@ class Customer < User; end
67
67
 
68
68
  class Fake < ActiveRecord::Base
69
69
  include SpecHelper
70
- col :name, :surname
71
- col :category, :group, :as => :references
70
+ col :name
71
+ col :surname
72
+ col :category_id, :type => :integer
73
+ col :group_id, :type => :integer
72
74
  end
73
75
 
74
76
  class AutomobileMakeModelYearVariant < ActiveRecord::Base
data/spec/mysql_spec.rb CHANGED
@@ -16,7 +16,7 @@ system %{#{cmd} -e "drop database #{database}"}
16
16
  system %{#{cmd} -e "create database #{database}"}
17
17
 
18
18
  ActiveRecord::Base.establish_connection(
19
- 'adapter' => 'mysql',
19
+ 'adapter' => (RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'),
20
20
  'encoding' => 'utf8',
21
21
  'database' => database,
22
22
  'username' => username,
@@ -26,6 +26,6 @@ ActiveRecord::Base.establish_connection(
26
26
  # require 'logger'
27
27
  # ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
28
28
 
29
- require File.expand_path('../models.rb', __FILE__)
29
+ # require File.expand_path('../models.rb', __FILE__)
30
30
 
31
31
  require File.expand_path('../shared_examples.rb', __FILE__)
@@ -13,13 +13,11 @@ ActiveRecord::Base.establish_connection(
13
13
  'adapter' => 'postgresql',
14
14
  'encoding' => 'utf8',
15
15
  'database' => database,
16
- 'username' => username,
16
+ 'username' => username
17
17
  # 'password' => password
18
18
  )
19
19
 
20
20
  # require 'logger'
21
21
  # ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
22
22
 
23
- require File.expand_path('../models.rb', __FILE__)
24
-
25
23
  require File.expand_path('../shared_examples.rb', __FILE__)
@@ -5,61 +5,9 @@ describe ActiveRecordInlineSchema do
5
5
  end
6
6
  end
7
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
8
  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
9
+ (!!ActiveRecord::Base.connection.table_exists?(Post.table_name)).must_equal false
10
+ (!!ActiveRecord::Base.connection.table_exists?(Category.table_name)).must_equal false
63
11
  Post.auto_upgrade!; Category.auto_upgrade!
64
12
  Post.column_names.sort.must_equal Post.db_columns
65
13
  Category.column_names.sort.must_equal Category.schema_columns
@@ -74,10 +22,10 @@ describe ActiveRecordInlineSchema do
74
22
 
75
23
 
76
24
  # Remove a column
77
- Post.reset_table_definition!
25
+ Post.inline_schema_config.clear
78
26
  Post.class_eval do
79
27
  col :name
80
- col :category, :as => :references
28
+ col :category_id, :type => :integer
81
29
  end
82
30
  Post.auto_upgrade!
83
31
  post = Post.first
@@ -90,19 +38,21 @@ describe ActiveRecordInlineSchema do
90
38
  # Check indexes
91
39
  Animal.auto_upgrade!
92
40
  Animal.db_indexes.size.must_be :>, 0
93
- Animal.db_indexes.must_equal Animal.indexes.keys.sort
41
+ Animal.db_indexes.must_equal Animal.schema_indexes
94
42
 
95
43
  indexes_was = Animal.db_indexes
96
44
 
97
45
  # Remove an index
98
- Animal.indexes.delete(indexes_was.pop)
46
+ target = indexes_was.pop
47
+ Animal.inline_schema_config.ideal_indexes.delete_if { |ideal_index| ideal_index.name.to_s == target.to_s }
99
48
  Animal.auto_upgrade!
100
- Animal.indexes.keys.sort.must_equal indexes_was
49
+ Animal.schema_indexes.sort.must_equal indexes_was
101
50
  Animal.db_indexes.must_equal indexes_was
102
51
 
103
52
  # Add a new index
104
53
  Animal.class_eval do
105
- col :category, :as => :references, :index => true
54
+ col :category_id, :type => :integer
55
+ add_index :category_id
106
56
  end
107
57
  Animal.auto_upgrade!
108
58
  Animal.db_columns.must_include "category_id"
@@ -111,7 +61,7 @@ describe ActiveRecordInlineSchema do
111
61
 
112
62
  it 'works with STI' do
113
63
  Pet.auto_upgrade!
114
- Pet.reset_column_information
64
+ Pet.safe_reset_column_information
115
65
  Pet.db_columns.must_include "type"
116
66
  Dog.auto_upgrade!
117
67
  Pet.db_columns.must_include "type"
@@ -129,8 +79,7 @@ describe ActiveRecordInlineSchema do
129
79
  Dog.first.name.must_equal "bar"
130
80
 
131
81
  # What's happen if we change schema?
132
- Dog.table_definition.must_equal Pet.table_definition
133
- Dog.indexes.must_equal Pet.indexes
82
+ Dog.schema_indexes.must_equal Pet.schema_indexes
134
83
  Dog.class_eval do
135
84
  col :bau
136
85
  end
@@ -142,6 +91,7 @@ describe ActiveRecordInlineSchema do
142
91
 
143
92
  it 'works with custom inheritance column' do
144
93
  User.auto_upgrade!
94
+ User.inheritance_column.must_equal 'role' # known to fail on rails 3
145
95
  Administrator.create(:name => "Davide", :surname => "D'Agostino")
146
96
  Customer.create(:name => "Foo", :surname => "Bar")
147
97
  Administrator.count.must_equal 1
@@ -149,8 +99,8 @@ describe ActiveRecordInlineSchema do
149
99
  Customer.count.must_equal 1
150
100
  Customer.first.name.must_equal "Foo"
151
101
  User.count.must_equal 2
152
- User.first.role.must_equal "Administrator"
153
- User.last.role.must_equal "Customer"
102
+ User.find_by_name('Davide').role.must_equal "Administrator"
103
+ User.find_by_name('Foo').role.must_equal "Customer"
154
104
  end
155
105
 
156
106
  it 'allow multiple columns definitions' do
@@ -202,17 +152,17 @@ describe ActiveRecordInlineSchema do
202
152
  it 'is idempotent' do
203
153
  ActiveRecord::Base.descendants.each do |active_record|
204
154
  active_record.auto_upgrade!
205
- active_record.reset_column_information
155
+ active_record.safe_reset_column_information
206
156
  before = [ active_record.db_columns, active_record.db_indexes ]
207
157
  active_record.auto_upgrade!
208
- active_record.reset_column_information
158
+ active_record.safe_reset_column_information
209
159
  [ active_record.db_columns, active_record.db_indexes ].must_equal before
210
160
  active_record.auto_upgrade!
211
- active_record.reset_column_information
161
+ active_record.safe_reset_column_information
212
162
  active_record.auto_upgrade!
213
- active_record.reset_column_information
163
+ active_record.safe_reset_column_information
214
164
  active_record.auto_upgrade!
215
- active_record.reset_column_information
165
+ active_record.safe_reset_column_information
216
166
  [ active_record.db_columns, active_record.db_indexes ].must_equal before
217
167
  end
218
168
  end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,19 @@
1
- require 'rubygems' unless defined?(Gem)
1
+ require 'rubygems'
2
2
  require 'bundler/setup'
3
- require 'active_record_inline_schema'
3
+
4
+ if ::Bundler.definition.specs['debugger'].first
5
+ require 'debugger'
6
+ elsif ::Bundler.definition.specs['ruby-debug'].first
7
+ require 'ruby-debug'
8
+ end
9
+
10
+ require 'minitest/spec'
4
11
  require 'minitest/autorun'
12
+ require 'minitest/reporters'
13
+ MiniTest::Unit.runner = MiniTest::SuiteRunner.new
14
+ MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
15
+
16
+ require 'active_record_inline_schema'
5
17
 
6
18
  # require 'logger'
7
19
  # ActiveRecord::Base.logger = Logger.new($stderr)
@@ -22,7 +34,20 @@ module SpecHelper
22
34
  end
23
35
 
24
36
  def schema_columns
25
- table_definition.columns.map { |c| c.name.to_s }.sort
37
+ inline_schema_config.ideal_columns.map { |c| c.name.to_s }.sort
38
+ end
39
+
40
+ def schema_indexes
41
+ inline_schema_config.ideal_indexes.map { |c| c.name.to_s }.sort
42
+ end
43
+
44
+ def safe_reset_column_information
45
+ if connection.respond_to?(:schema_cache)
46
+ connection.schema_cache.clear!
47
+ end
48
+ reset_column_information
26
49
  end
27
50
  end
28
51
  end
52
+
53
+ require File.expand_path('../models.rb', __FILE__)
data/spec/sqlite3_spec.rb CHANGED
@@ -5,6 +5,4 @@ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':me
5
5
  # require 'logger'
6
6
  # ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new($stdout)
7
7
 
8
- require File.expand_path('../models.rb', __FILE__)
9
-
10
8
  require File.expand_path('../shared_examples.rb', __FILE__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_inline_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,43 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-03-26 00:00:00.000000000 Z
13
+ date: 2012-04-19 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: lock_method
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: activesupport
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
15
47
  - !ruby/object:Gem::Dependency
16
48
  name: activerecord
17
- requirement: &2166623420 !ruby/object:Gem::Requirement
49
+ requirement: !ruby/object:Gem::Requirement
18
50
  none: false
19
51
  requirements:
20
52
  - - ! '>='
@@ -22,7 +54,12 @@ dependencies:
22
54
  version: '3'
23
55
  type: :runtime
24
56
  prerelease: false
25
- version_requirements: *2166623420
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '3'
26
63
  description: Specify columns like you would with ActiveRecord migrations and then
27
64
  run .auto_upgrade! Based on the mini_record gem from Davide D'Agostino, it adds
28
65
  fewer aliases, doesn't create timestamps and relationship columns automatically.
@@ -35,12 +72,16 @@ extra_rdoc_files: []
35
72
  files:
36
73
  - .gitignore
37
74
  - .travis.yml
75
+ - CHANGELOG
38
76
  - Gemfile
39
77
  - README.md
40
78
  - Rakefile
41
79
  - active_record_inline_schema.gemspec
42
80
  - lib/active_record_inline_schema.rb
43
- - lib/active_record_inline_schema/auto_schema.rb
81
+ - lib/active_record_inline_schema/active_record_class_methods.rb
82
+ - lib/active_record_inline_schema/config.rb
83
+ - lib/active_record_inline_schema/config/column.rb
84
+ - lib/active_record_inline_schema/config/index.rb
44
85
  - lib/active_record_inline_schema/version.rb
45
86
  - spec/models.rb
46
87
  - spec/mysql_spec.rb
@@ -68,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
109
  version: '0'
69
110
  requirements: []
70
111
  rubyforge_project:
71
- rubygems_version: 1.8.15
112
+ rubygems_version: 1.8.21
72
113
  signing_key:
73
114
  specification_version: 3
74
115
  summary: Define table structure (columns and indexes) inside your ActiveRecord models
@@ -1,229 +0,0 @@
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