force_schema 0.1.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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ rdoc/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in force_schema.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,44 @@
1
+ =force_schema
2
+
3
+ class Car < ActiveRecord::Base
4
+ set_primary_key :name
5
+ force_schema do
6
+ string 'name' # Nissan Altima, will automatically be indexed because it's the primary key
7
+ string 'make_name' # Nissan
8
+ string 'model_name' # Altime
9
+ float 'fuel_efficiency_city'
10
+ string 'fuel_efficiency_city_units'
11
+ float 'fuel_efficiency_highway'
12
+ string 'fuel_efficiency_highway_units'
13
+ integer 'year'
14
+ datetime 'released_at'
15
+ date 'released_on'
16
+ index ['name', 'make_name']
17
+ end
18
+ end
19
+
20
+ == Warning: data loss
21
+
22
+ If you call <tt>Car.force_schema!</tt>, it may destroy data by adding, removing, or changing the type of columns. <b>No attempt is made to preserve data.</b>
23
+
24
+ == What it synchronizes
25
+
26
+ * adds missing columns
27
+ * adds missing indexes
28
+ * removes unrecognized columns
29
+ * removes unrecognized indexes
30
+ * changes the type of columns if it doesn't match what you asked for
31
+ * changes the columns indexed if they don't match what you asked for
32
+ * makes sure default value is synced up
33
+
34
+ == Supported databases
35
+
36
+ * MySQL is tested
37
+ * SQLite is tested
38
+ * Postgres is not supported
39
+
40
+ == History
41
+
42
+ Extracted from https://github.com/seamusabshere/data_miner. In production use at http://carbon.brighterplanet.com and http://data.brighterplanet.com.
43
+
44
+ Copyright 2011 Seamus Abshere
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'lib' << 'test'
8
+ test.pattern = 'test/**/test_*.rb'
9
+ test.verbose = true
10
+ end
11
+
12
+ require 'rake/rdoctask'
13
+ Rake::RDocTask.new do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'force_schema'
16
+ rdoc.options << '--line-numbers' << '--inline-source'
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
20
+
21
+ task :test_separately do
22
+ puts `bundle exec rake test TEST=test/test_mysql.rb`
23
+ puts `bundle exec rake test TEST=test/test_sqlite3.rb`
24
+ end
25
+
26
+ task :default => :test_separately
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "force_schema/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "force_schema"
7
+ s.version = ForceSchema::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Seamus Abshere"]
10
+ s.email = ["seamus@abshere.net"]
11
+ s.homepage = "https://github.com/seamusabshere/force_schema"
12
+ s.summary = %q{Sometimes you don't need to write up and down migrations, you just want a table (aka schema) to have a certain structure.}
13
+ s.description = %q{Declare a table structure like an ActiveRecord migration and run 'force_schema!' whenever you want. For when you don't need up and down migrations.}
14
+
15
+ s.rubyforge_project = "force_schema"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency 'test-unit'
23
+ s.add_development_dependency 'mysql'
24
+ s.add_development_dependency 'sqlite3-ruby'
25
+ s.add_development_dependency 'rake'
26
+ s.add_dependency 'activerecord', '>=2.3.10'
27
+ s.add_dependency 'activesupport', '>=2.3.10'
28
+ s.add_dependency 'blockenspiel'
29
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_record'
2
+ require 'active_support/core_ext'
3
+
4
+ module ForceSchema
5
+ autoload :Registry, 'force_schema/registry'
6
+ autoload :Schema, 'force_schema/schema'
7
+
8
+ def force_schema!
9
+ enforced_schema.run
10
+ end
11
+
12
+ def force_schema(create_table_options = {}, &blk)
13
+ enforced_schema.create_table_options = create_table_options
14
+ ::Blockenspiel.invoke blk, enforced_schema
15
+ enforced_schema
16
+ end
17
+
18
+ def enforced_schema
19
+ Registry.instance[name] ||= Schema.new self
20
+ end
21
+ end
22
+
23
+ ::ActiveRecord::Base.extend ::ForceSchema
@@ -0,0 +1,6 @@
1
+ require 'singleton'
2
+ module ForceSchema
3
+ class Registry < ::Hash
4
+ include ::Singleton
5
+ end
6
+ end
@@ -0,0 +1,279 @@
1
+ require 'blockenspiel'
2
+ require 'zlib'
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ module ForceSchema
6
+ class Schema
7
+ MAX_INDEX_NAME_LENGTH = 32 # mysql
8
+
9
+ class << self
10
+ def suggest_index_name(active_record, columns, index_options = {})
11
+ return index_options[:name] if index_options.has_key? :name
12
+ default_name = 'index_' + ::Array.wrap(columns).join('_and_') #active_record.connection.index_name(active_record.table_name, index_options.merge(:column => columns))
13
+ if default_name.length < MAX_INDEX_NAME_LENGTH
14
+ default_name
15
+ else
16
+ default_name[0..MAX_INDEX_NAME_LENGTH-11] + ::Zlib.crc32(default_name).to_s
17
+ end
18
+ end
19
+ end
20
+
21
+ include ::Blockenspiel::DSL
22
+
23
+ attr_reader :active_record
24
+ attr_reader :create_table_options
25
+
26
+ def initialize(active_record)
27
+ @active_record = active_record
28
+ end
29
+
30
+ def create_table_options=(options = {})
31
+ @create_table_options = options.symbolize_keys
32
+ raise ":id => true is not allowed in create_table_options." if create_table_options[:id] === true
33
+ raise ":primary_key is not allowed in create_table_options. Use set_primary_key instead." if create_table_options.has_key?(:primary_key)
34
+ if create_table_options[:options].blank? and mysql?
35
+ create_table_options[:options] = 'ENGINE=INNODB CHARSET=UTF8 COLLATE=UTF8_GENERAL_CI'
36
+ end
37
+ create_table_options[:id] = false # always
38
+ end
39
+
40
+ # sabshere 1/25/11 lifted straight from activerecord-3.0.3/lib/active_record/connection_adapters/abstract/schema_definitions.rb
41
+ %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
42
+ class_eval <<-EOV
43
+ def #{column_type}(*args) # def string(*args)
44
+ options = args.extract_options! # options = args.extract_options!
45
+ column_names = args # column_names = args
46
+ #
47
+ column_names.each { |name| ideal_table.column(name, '#{column_type}', options) } # column_names.each { |name| ideal_table.column(name, 'string', options) }
48
+ end # end
49
+ EOV
50
+ end
51
+
52
+ def index(columns, index_options = {})
53
+ index_options = index_options.symbolize_keys
54
+ columns = ::Array.wrap columns
55
+ index_name = Schema.suggest_index_name active_record, columns, index_options
56
+ index_unique = index_options.has_key?(:unique) ? index_options[:unique] : true
57
+ ideal_indexes.push ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, index_unique, columns)
58
+ nil
59
+ end
60
+
61
+ def run
62
+ _create_table
63
+ _set_primary_key
64
+ _remove_columns
65
+ _add_columns
66
+ _remove_indexes
67
+ _add_indexes
68
+ nil
69
+ end
70
+
71
+ private
72
+
73
+ def log_debug(msg)
74
+ ::ActiveRecord::Base.logger.try :debug, msg
75
+ end
76
+
77
+ def column(*args)
78
+ ideal_table.column(*args)
79
+ end
80
+
81
+ def ideal_primary_key_name
82
+ active_record.primary_key.to_s
83
+ end
84
+
85
+ def actual_primary_key_name
86
+ if sqlite? and pk = active_record.columns_hash.detect { |k, v| v.primary }
87
+ pk[1].name
88
+ elsif pk = connection.primary_key(table_name)
89
+ pk
90
+ end.to_s
91
+ end
92
+
93
+ def index_equivalent?(a, b)
94
+ return false unless a and b
95
+ %w{ name columns }.all? do |property|
96
+ a_property = ::Array.wrap(a.send(property)).map(&:to_s)
97
+ b_property = ::Array.wrap(b.send(property)).map(&:to_s)
98
+ log_debug "...comparing #{a_property.inspect} <-> #{b_property.inspect}"
99
+ a_property == b_property
100
+ end
101
+ end
102
+
103
+ COMPARABLE_OPTIONS = [ :default ] # other things vary rather unpredicatably by adapter
104
+
105
+ def column_equivalent?(a, b)
106
+ return false unless a and b
107
+ a_type = (a.type.to_s == 'primary_key') ? 'integer' : a.type.to_s
108
+ b_type = (b.type.to_s == 'primary_key') ? 'integer' : b.type.to_s
109
+ a_type == b_type and a.name.to_s == b.name.to_s and column_options(a).slice(COMPARABLE_OPTIONS) == column_options(b).slice(COMPARABLE_OPTIONS)
110
+ end
111
+
112
+ %w{ column index }.each do |i|
113
+ eval %{
114
+ def #{i}_needs_to_be_placed?(name)
115
+ actual = actual_#{i} name
116
+ return true unless actual
117
+ ideal = ideal_#{i} name
118
+ not #{i}_equivalent? actual, ideal
119
+ end
120
+
121
+ def #{i}_needs_to_be_removed?(name)
122
+ ideal_#{i}(name).nil?
123
+ end
124
+ }
125
+ end
126
+
127
+ def ideal_column(name)
128
+ ideal_table[name.to_s]
129
+ end
130
+
131
+ def actual_column(name)
132
+ active_record.columns_hash[name.to_s]
133
+ end
134
+
135
+ def ideal_index(name)
136
+ ideal_indexes.detect { |ideal| ideal.name == name.to_s }
137
+ end
138
+
139
+ def actual_index(name)
140
+ actual_indexes.detect { |actual| actual.name == name.to_s }
141
+ end
142
+
143
+ POSSIBLE_OPTIONS = [ :limit, :default, :null, :precision, :scale ]
144
+
145
+ def column_options(column)
146
+ POSSIBLE_OPTIONS.inject({}) do |memo, k|
147
+ v = column.send(k)
148
+ if v == false or v.present?
149
+ memo[k] = v
150
+ end
151
+ memo
152
+ end
153
+ end
154
+
155
+ def place_column(name)
156
+ remove_column name if actual_column name
157
+ ideal = ideal_column name
158
+ options = column_options ideal
159
+ log_debug "PLACING COLUMN #{name}"
160
+ connection.add_column table_name, name, ideal.type.to_sym, options
161
+ active_record.reset_column_information
162
+ end
163
+
164
+ def remove_column(name)
165
+ log_debug "REMOVING COLUMN #{name}"
166
+ connection.remove_column table_name, name
167
+ active_record.reset_column_information
168
+ end
169
+
170
+ def place_index(name)
171
+ remove_index name if actual_index name
172
+ ideal = ideal_index name
173
+ log_debug "PLACING INDEX #{name}"
174
+ connection.add_index table_name, ideal.columns, :name => ideal.name
175
+ active_record.reset_column_information
176
+ end
177
+
178
+ def remove_index(name)
179
+ log_debug "REMOVING INDEX #{name}"
180
+ connection.remove_index table_name, :name => name
181
+ active_record.reset_column_information
182
+ end
183
+
184
+ # sabshere 1/25/11 what if there were multiple connections
185
+ # blockenspiel doesn't like to delegate this to #active_record
186
+ def connection
187
+ ::ActiveRecord::Base.connection
188
+ end
189
+
190
+ def table_name
191
+ active_record.table_name
192
+ end
193
+
194
+ def ideal_table
195
+ @ideal_table ||= ::ActiveRecord::ConnectionAdapters::TableDefinition.new connection
196
+ end
197
+
198
+ def ideal_indexes
199
+ @ideal_indexes ||= []
200
+ end
201
+
202
+ def actual_indexes
203
+ connection.indexes table_name
204
+ end
205
+
206
+ def _create_table
207
+ if not active_record.table_exists?
208
+ log_debug "CREATING TABLE #{table_name} with #{create_table_options.inspect}"
209
+ connection.create_table table_name, create_table_options do |t|
210
+ t.integer 'force_schema_tmp'
211
+ end
212
+ active_record.reset_column_information
213
+ end
214
+ end
215
+
216
+ def _set_primary_key
217
+ if ideal_primary_key_name == 'id' and not ideal_column('id')
218
+ log_debug "no special primary key set on #{table_name}, so using 'id'"
219
+ column 'id', :primary_key # needs to be a sym?
220
+ end
221
+ actual = actual_column actual_primary_key_name
222
+ ideal = ideal_column ideal_primary_key_name
223
+ if not column_equivalent? actual, ideal
224
+ log_debug "looks like #{table_name} has a bad (or missing) primary key"
225
+ if actual
226
+ log_debug "looks like primary key needs to change from #{actual_primary_key_name} to #{ideal_primary_key_name}, re-creating #{table_name} from scratch"
227
+ connection.drop_table table_name
228
+ active_record.reset_column_information
229
+ _create_table
230
+ end
231
+ place_column ideal_primary_key_name
232
+ unless ideal.type.to_s == 'primary_key'
233
+ log_debug "SETTING #{ideal_primary_key_name} AS PRIMARY KEY"
234
+ if sqlite?
235
+ special_sqlite_primary_key_index_name = "IDX_#{table_name}_#{ideal_primary_key_name}"
236
+ connection.execute "CREATE UNIQUE INDEX #{special_sqlite_primary_key_index_name} ON #{table_name} (#{ideal_primary_key_name} ASC)"
237
+ ideal_indexes.push ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, special_sqlite_primary_key_index_name, true, ideal_primary_key_name)
238
+ else
239
+ connection.execute "ALTER TABLE `#{table_name}` ADD PRIMARY KEY (`#{ideal_primary_key_name}`)"
240
+ end
241
+ end
242
+ end
243
+ active_record.reset_column_information
244
+ end
245
+
246
+ def sqlite?
247
+ connection.adapter_name.downcase.start_with? 'sqlite'
248
+ end
249
+
250
+ def mysql?
251
+ connection.adapter_name.downcase.start_with? 'mysql'
252
+ end
253
+
254
+ def _remove_columns
255
+ active_record.columns_hash.values.each do |actual|
256
+ remove_column actual.name if column_needs_to_be_removed? actual.name
257
+ end
258
+ end
259
+
260
+ def _add_columns
261
+ ideal_table.columns.each do |ideal|
262
+ place_column ideal.name if column_needs_to_be_placed? ideal.name
263
+ end
264
+ end
265
+
266
+ def _remove_indexes
267
+ actual_indexes.each do |actual|
268
+ remove_index actual.name if index_needs_to_be_removed? actual.name
269
+ end
270
+ end
271
+
272
+ def _add_indexes
273
+ ideal_indexes.each do |ideal|
274
+ next if ideal.name == ideal_primary_key_name # this should already have been taken care of
275
+ place_index ideal.name if index_needs_to_be_placed? ideal.name
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,3 @@
1
+ module ForceSchema
2
+ VERSION = "0.1.0"
3
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+ require 'logger'
5
+ require 'test/unit'
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ require 'force_schema'
9
+ require 'support/shared'
10
+ class Test::Unit::TestCase
11
+ end
12
+
13
+ ActiveRecord::Base.logger = Logger.new($stderr)
14
+ ActiveRecord::Base.logger.level = Logger::DEBUG
@@ -0,0 +1,18 @@
1
+ class Car < ActiveRecord::Base
2
+ set_primary_key :name
3
+
4
+ force_schema do
5
+ string 'name' # Nissan Altima, will automatically be indexed because it's the primary key
6
+ string 'make_name' # Nissan
7
+ string 'model_name' # Altime
8
+ float 'fuel_efficiency_city'
9
+ string 'fuel_efficiency_city_units'
10
+ float 'fuel_efficiency_highway'
11
+ string 'fuel_efficiency_highway_units'
12
+ integer 'year'
13
+ datetime 'released_at'
14
+ date 'released_on'
15
+ boolean 'consumer_reports_best_buy', :default => false
16
+ index ['name', 'make_name']
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ class Monk < ActiveRecord::Base
2
+ set_primary_key :name
3
+
4
+ force_schema do
5
+ string 'name'
6
+ end
7
+ end
@@ -0,0 +1,103 @@
1
+ module Shared
2
+ def setup
3
+ ActiveRecord::Base.connection.drop_table 'cars' rescue nil
4
+ Car.force_schema!
5
+ end
6
+
7
+ def test_001_add_columns_and_indexes_from_scratch
8
+ assert_equal :string, ct(Car, :name)
9
+ assert_equal :float, ct(Car, :fuel_efficiency_city)
10
+ assert_equal :integer, ct(Car, :year)
11
+ assert_equal :datetime, ct(Car, :released_at)
12
+ assert_equal :date, ct(Car, :released_on)
13
+ assert index?(Car, :index_name_and_make_name)
14
+ end
15
+
16
+ def test_002_restore_removed_column
17
+ Car.connection.remove_column :cars, :year
18
+ assert_equal nil, ct(Car, :year)
19
+ Car.force_schema!
20
+ assert_equal :integer, ct(Car, :year)
21
+ end
22
+
23
+ def test_003_fix_column_type
24
+ Car.connection.remove_column :cars, :year
25
+ Car.connection.add_column :cars, :year, :string
26
+ Car.reset_column_information
27
+ assert_equal :string, ct(Car, :year)
28
+ Car.force_schema!
29
+ assert_equal :integer, ct(Car, :year)
30
+ end
31
+
32
+ def test_004_remove_unrecognized_column
33
+ Car.connection.add_column :cars, :foobar, :string
34
+ assert_equal :string, ct(Car, :foobar)
35
+ Car.force_schema!
36
+ assert_equal nil, ct(Car, :foobar)
37
+ end
38
+
39
+ def test_005_restore_removed_index
40
+ Car.connection.remove_index :cars, :name => 'index_name_and_make_name'
41
+ Car.reset_column_information
42
+ assert !index?(Car, :index_name_and_make_name)
43
+ Car.force_schema!
44
+ assert index?(Car, :index_name_and_make_name)
45
+ end
46
+
47
+ def test_006_restore_damaged_index
48
+ Car.connection.remove_column :cars, :make_name
49
+ Car.force_schema!
50
+ assert index?(Car, :index_name_and_make_name)
51
+ end
52
+
53
+ def test_007_remove_unrecognized_index
54
+ Car.connection.add_index :cars, :year, :name => 'foobar'
55
+ Car.reset_column_information
56
+ assert index?(Car, :foobar)
57
+ Car.force_schema!
58
+ assert !index?(Car, :foobar)
59
+ end
60
+
61
+ def test_008_primary_key_is_unique
62
+ a = Car.new
63
+ a.name = 'a'
64
+ a.save!
65
+ assert_raises(ActiveRecord::RecordNotUnique) do
66
+ a = Car.new
67
+ a.name = 'a'
68
+ a.save!
69
+ end
70
+ end
71
+
72
+ def test_009_edge_case_one_column
73
+ ActiveRecord::Base.connection.drop_table 'monks' rescue nil
74
+ Monk.force_schema!
75
+ Monk.force_schema!
76
+ Monk.force_schema!
77
+ assert_equal :string, ct(Monk, :name)
78
+ end
79
+
80
+ def test_010_default_false
81
+ a = Car.new
82
+ assert_equal false, a.consumer_reports_best_buy
83
+ end
84
+
85
+ private
86
+
87
+ def ct(active_record, column_name)
88
+ if c = active_record.columns_hash[column_name.to_s]
89
+ c.type
90
+ end
91
+ end
92
+
93
+ def index?(active_record, index_name)
94
+ active_record.reset_column_information
95
+ index_name = index_name.to_s
96
+ return true if index_name == active_record.primary_key.to_s
97
+ if ActiveRecord::VERSION::MAJOR < 3
98
+ active_record.connection.index_exists?(active_record.table_name, index_name, :not_supported_by_adapter)
99
+ else
100
+ active_record.connection.index_exists?(active_record.table_name, nil, :name => index_name)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,16 @@
1
+ require 'helper'
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ 'adapter' => 'mysql',
5
+ 'database' => 'test_force_schema',
6
+ 'username' => 'root',
7
+ 'password' => 'password',
8
+ 'encoding' => 'utf8'
9
+ )
10
+
11
+ require 'support/car'
12
+ require 'support/monk'
13
+
14
+ class TestMysql < Test::Unit::TestCase
15
+ include Shared
16
+ end
@@ -0,0 +1,10 @@
1
+ require 'helper'
2
+
3
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
4
+
5
+ require 'support/car'
6
+ require 'support/monk'
7
+
8
+ class TestSqlite3 < Test::Unit::TestCase
9
+ include Shared
10
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: force_schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Seamus Abshere
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-19 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: test-unit
16
+ requirement: &2154422840 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2154422840
25
+ - !ruby/object:Gem::Dependency
26
+ name: mysql
27
+ requirement: &2154422420 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2154422420
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3-ruby
38
+ requirement: &2154422000 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2154422000
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &2154421580 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2154421580
58
+ - !ruby/object:Gem::Dependency
59
+ name: activerecord
60
+ requirement: &2154421080 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 2.3.10
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *2154421080
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: &2154420580 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 2.3.10
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *2154420580
80
+ - !ruby/object:Gem::Dependency
81
+ name: blockenspiel
82
+ requirement: &2154420200 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *2154420200
91
+ description: Declare a table structure like an ActiveRecord migration and run 'force_schema!'
92
+ whenever you want. For when you don't need up and down migrations.
93
+ email:
94
+ - seamus@abshere.net
95
+ executables: []
96
+ extensions: []
97
+ extra_rdoc_files: []
98
+ files:
99
+ - .gitignore
100
+ - Gemfile
101
+ - README.rdoc
102
+ - Rakefile
103
+ - force_schema.gemspec
104
+ - lib/force_schema.rb
105
+ - lib/force_schema/registry.rb
106
+ - lib/force_schema/schema.rb
107
+ - lib/force_schema/version.rb
108
+ - test/helper.rb
109
+ - test/support/car.rb
110
+ - test/support/monk.rb
111
+ - test/support/shared.rb
112
+ - test/test_mysql.rb
113
+ - test/test_sqlite3.rb
114
+ homepage: https://github.com/seamusabshere/force_schema
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project: force_schema
134
+ rubygems_version: 1.8.5
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Sometimes you don't need to write up and down migrations, you just want a
138
+ table (aka schema) to have a certain structure.
139
+ test_files:
140
+ - test/helper.rb
141
+ - test/support/car.rb
142
+ - test/support/monk.rb
143
+ - test/support/shared.rb
144
+ - test/test_mysql.rb
145
+ - test/test_sqlite3.rb