force_schema 0.1.0

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