active_record_inline_schema 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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