arborist-rails 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cdaa1c3f36f75f8d4c3ef4049bcd7775208b3055
4
+ data.tar.gz: e044762cbc071aaed0f5209d5b45b9e221b49039
5
+ SHA512:
6
+ metadata.gz: 73642c5648a2e03503be97a996430dcf14bab7e50150fac4ff0e479993e44d698d292a8cc78c4748e1746a9b4e2acee66260d9d3635deb61d3276b28ff0e6a8a
7
+ data.tar.gz: fe228e6fd604100982deb19e1f4807487f7a1cc41ace4f5e73e407763da613286daa2480e5f0f6bd603d380f0c6e43444e08ac3649c3f1188c0a1eff6158eaca
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ Guardfile
@@ -0,0 +1,20 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ Exclude:
4
+ - 'db/**/*'
5
+ - 'node_modules/**/*'
6
+ - 'vendor/**/*'
7
+ - Guardfile
8
+ DisplayCopNames: true
9
+ Rails:
10
+ Enabled: true
11
+ Style/Documentation:
12
+ Enabled: false
13
+ Style/HashSyntax:
14
+ Exclude:
15
+ - Rakefile
16
+ - '**/*.rake'
17
+ Style/LambdaCall:
18
+ Enabled: false
19
+ Style/MultilineMethodCallIndentation:
20
+ Enabled: false
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in arborist.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Adam Cuppy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,317 @@
1
+ # Arborist
2
+
3
+ ## Usage
4
+
5
+ `Arborist::Migration` is meant to run as a drop-in replacement to
6
+ `ActiveRecord::Migration`. The easiest way to do that is modify your
7
+ migrations to inherit from `Arborist::Migration`
8
+
9
+ ```ruby
10
+ class AddAdminToUser < Arborist::Migration
11
+ data do
12
+ # forward
13
+ end
14
+
15
+ def change
16
+ add_column :users, :admin, :boolean
17
+ end
18
+ end
19
+ ```
20
+
21
+ By default `data` takes a forward-only approach and assumes by rolling back
22
+ the schema would automatically revert the data migration. However, you can
23
+ declare both `up` and `down` migrations with similar corresponding method
24
+ options:
25
+
26
+ ```ruby
27
+ class AddAdminToUser < Arborist::Migration
28
+ data :up do # => default
29
+ # forward
30
+ end
31
+
32
+ data :down do
33
+ # rollback
34
+ end
35
+
36
+ def change
37
+ add_column :users, :admin, :boolean
38
+ end
39
+ end
40
+ ```
41
+
42
+ ### Helpers
43
+
44
+ Although all `ActiveRecord::Migration` methods are supported, there are a set
45
+ of helpers to define one action against another.
46
+
47
+ ```ruby
48
+ class AddAdminToUser < Arborist::Migration
49
+ data do # => :up
50
+ # data only adjustments
51
+ end
52
+
53
+ schema do # => :change
54
+ add_column :users, :admin, :boolean
55
+ end
56
+ end
57
+ ```
58
+
59
+ Optionally, pass a migration message:
60
+
61
+ ```ruby
62
+ class AddAdminToUser < ActiveRecord::Migration
63
+ data say: 'Updating admin flag' do
64
+ # ...
65
+ end
66
+ # ...
67
+ end
68
+ ```
69
+
70
+ For more complex data migrations you can provide a class. The only
71
+ expectation is that the class being referenced includes a public `call`
72
+ method. And, like other previous implementations, you can provide options such
73
+ as `say`, `up` and `down` (to name a few).
74
+
75
+ ```ruby
76
+ class AddAdminToUser < Arborist::Migration
77
+ class UpdateAdminFlag
78
+ def call
79
+ # custom migration ...
80
+ end
81
+ end
82
+
83
+ data use: UpdateAdminFlag
84
+
85
+ def change
86
+ add_column :users, :admin, :boolean, default: false
87
+ end
88
+ end
89
+ ```
90
+
91
+ Similar to other uses of `data` additional configuration options can be
92
+ passed in following the
93
+
94
+ ### Interchangeable Models
95
+
96
+ A common 'best-practice' is to use raw SQL instead of `ActiveRecord` backed
97
+ classes, which is a totally practical option (see explanation below), but you
98
+ lose the power of `ActiveRecord`, *so what if we could use `ActiveRecord` to
99
+ support those changes?*
100
+
101
+ Interchangeable models can be powerful tool when tracking object references.
102
+
103
+ Instead of using the model directly, set the target model and use `model` in
104
+ the data migration:
105
+
106
+ ```ruby
107
+ class AddAdminToUser < Arborist::Migration
108
+ model :User
109
+
110
+ data do
111
+ model.find_each do |user|
112
+ user.admin = true
113
+ user.save!
114
+ end
115
+ end
116
+ # ...
117
+ end
118
+ ```
119
+
120
+ Further, if you need to reference multiple models, you can do so by setting a
121
+ method reference for each:
122
+
123
+ ```ruby
124
+ class AddAdminToUser < Arborist::Migration
125
+ model :User, as: :user
126
+ model :Company, as: :company
127
+
128
+ data do
129
+ user.all # ...
130
+ company.all # ...
131
+ end
132
+ # ...
133
+ end
134
+ ```
135
+
136
+ #### Now why not just reference the model directly?
137
+
138
+ Answer, Arborist intelligently detects if a model reference is missing (i.e.
139
+ removed at a later iteration) and provides a set of fallback options.
140
+
141
+ The most common strategy is to forward all model requests to the renamed model:
142
+
143
+ ```ruby
144
+ # ...
145
+ end
146
+ ```
147
+
148
+
149
+ ```ruby
150
+ # ...
151
+ end
152
+ ```
153
+
154
+ If this becomes confusing, that's okay. `Arborist` runs a built in linter
155
+ (`Arborist::Migration.lint!`) prior to migrating to confirm that all models
156
+ and attribute dependencies *being referenced* are available. If any failure
157
+ exists, the migration will fail *prior* to running all the migrations.
158
+
159
+ ### Failure
160
+
161
+ `Arborist` will suggest a data migration for the model reference, either in the
162
+ form of an addition to the offending migration...
163
+
164
+ Add to migration 'db/migrate/1234567890_add_admin_to_person.rb':
165
+
166
+ ```ruby
167
+ class AddAdminToPerson < Arborist::Migration
168
+ model :User => '...'
169
+ end
170
+ ```
171
+
172
+ ... or to generate a new data migration to fix the problem:
173
+
174
+ `$ rails g data_migration:model User`
175
+
176
+ Which generates:
177
+
178
+ ```ruby
179
+ class UpdateReferenceForUserModel < Arborist::Migration
180
+ model :User => '...'
181
+ end
182
+ ```
183
+
184
+ ## Testing
185
+
186
+ By abstracting all larger migration routines to a nested class, we can test
187
+ those as Ruby objects.
188
+
189
+ With `RSpec` we can use a bank of custom matcher:
190
+
191
+ ```rspec
192
+ require 'rails_helper'
193
+ require_migration 'add_admin_to_user' # Note: Do NOT include the datetime stamp
194
+
195
+ describe AddAdminToUser::Data do
196
+ # ...
197
+ end
198
+ ```
199
+
200
+ ## Methodology
201
+
202
+ Data migration in a Rails application can be a serious pain. Whether you take
203
+ the strategy of including the data migration in the schema migrations...
204
+
205
+ ```ruby
206
+ class AddAdminToUser < ActiveRecord::Migration
207
+ def change
208
+ # Schema migration
209
+ add_column :users, :admin, :boolean, default: false
210
+
211
+ # Data migration
212
+ User.all.each do |user|
213
+ user.admin = true;
214
+ user.save!
215
+ end
216
+ end
217
+ end
218
+ ```
219
+
220
+ Which at the time of generating the migration, works without issue; however,
221
+ down the line, we rename the `User` model and neglect to update this migration.
222
+
223
+ In the future, when we run the entire set of migration (vs.
224
+ `rake db:schema:load`) and the `User` model is missing, the migrations
225
+ explode - this sucks.
226
+
227
+ ### Common Solutions
228
+
229
+ *Temporary Models*
230
+
231
+ ```ruby
232
+ class AddAdminToUser < ActiveRecord::Migration
233
+ # Temporary class
234
+ class User < ActiveRecord::Base
235
+ end
236
+
237
+ # ...
238
+ end
239
+ ```
240
+
241
+ And although this is a solution, this course of action results in duplicating
242
+ the interface. Additional issues can present themselves, because the `User`
243
+ model is under the `AddAdminToUser` namespace (`AddAdminToUser::User`), which
244
+ will present issues when setting a polymorphic association or following an
245
+ Single Table Inheritance (STI) model.
246
+
247
+ ### Raw SQL
248
+
249
+ A Rails independent strategy, you can use straight SQL. Then ActiveRecord
250
+ models are not needed, and the presence (or lack) of the model is irrelevant.
251
+
252
+ ```ruby
253
+ class AddAdminToUser < ActiveRecord::Migration
254
+ def change
255
+ # Schema migration
256
+ add_column :users, :admin, :boolean, default: false
257
+
258
+ # Data migration in raw SQL
259
+ execute <<-SQL
260
+ UPDATE `users` SET `users`.`admin` = true
261
+ SQL
262
+ end
263
+ end
264
+ ```
265
+
266
+ As pointed out by many, this doesn't have many downsides, other than database
267
+ syntax differences.
268
+
269
+ ### Rake tasks
270
+
271
+ If entirely opposed to including data migrations in the ActiveRecord migrations
272
+ themselves (all examples above), then it's common to create a one off rake
273
+ tasks, which would be run directly on the instance.
274
+
275
+ `$ rake data:add_admin_flag_to_current_users`
276
+
277
+ But it requires the command is run on all application instances and following
278
+ the appropriate migration (i.e. `AddAdminToUser`); which could be done via
279
+ the deployment hooks. However, you would be breaking the isolation of your
280
+ migrations (within `db/migrate`) and polluting `lib/tasks/` with one-off rake
281
+ tasks; requiring cleanup.
282
+
283
+ Other issues: testing a rake task can be challenging; and, in essence we're
284
+ exposing a production available routine that could cause serious issues, such
285
+ as adding the admin flag to all users.
286
+
287
+ Wouldn't it be nice if you could:
288
+
289
+ * Run data migrations side by side with the corresponding schema migration(s);
290
+ * Test the data migration routine;
291
+ * Optionally disable data migrations on an environment, such as production?
292
+
293
+ Sure, it would.
294
+
295
+ ## Installation
296
+
297
+ Add this line to your application's Gemfile:
298
+
299
+ ```ruby
300
+ gem 'arborist-rails'
301
+ ```
302
+
303
+ And then execute:
304
+
305
+ `$ bundle`
306
+
307
+ Or install it yourself as:
308
+
309
+ `$ gem install arborist`
310
+
311
+ ## Contributing
312
+
313
+ 1. Fork it ( https://github.com/{my-github-username}/arborist/fork )
314
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
315
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
316
+ 4. Push to the branch (`git push origin my-new-feature`)
317
+ 5. Create a new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'arborist/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "arborist-rails"
8
+ spec.version = Arborist::VERSION
9
+ spec.authors = ["Adam Cuppy"]
10
+ spec.email = ["adam@codingzeal.com"]
11
+ spec.summary = %q{Framework for working with data migrations and seeds
12
+ in a Rails application}
13
+ spec.homepage = 'https://github.com/CodingZeal/arborist'
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", ">= 3.2.0"
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "sqlite3"
26
+ spec.add_development_dependency "pry-byebug"
27
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+ require 'arborist/version'
3
+ require 'arborist/exceptions'
4
+ require 'arborist/configuration'
5
+ require 'arborist/migration'
6
+
7
+ module Arborist
8
+ end
@@ -0,0 +1,21 @@
1
+ require 'ostruct'
2
+
3
+ module Arborist
4
+ def self.config ns = nil
5
+ @config ||= Configuration.new
6
+ @config[ns] ||= Configuration.new if ns
7
+
8
+ if block_given?
9
+ yield ns ? @config[ns] : @config
10
+ end
11
+
12
+ @config
13
+ end
14
+
15
+ class Configuration < OpenStruct
16
+ def initialize props={}
17
+ super
18
+ yield self if block_given?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module Arborist
2
+ class ModelReferenceError < NameError
3
+ def initialize model_ref
4
+ super "#{model_ref} is not available"
5
+ end
6
+
7
+ def model
8
+ raise self
9
+ end
10
+ end
11
+
12
+ class UnknownSchemaMethod < ArgumentError
13
+ def initialize method_name
14
+ super %Q{Unknown schema migration method: #{method_name}.
15
+ Use :up, :down or :change}
16
+ end
17
+ end
18
+
19
+ class InheritanceError < StandardError
20
+ def initialize method_name
21
+ super %Q{ Method not available in ActiveRecord::Migration. Inherit from
22
+ Arborist::Migration to use #{method_name}}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'configuration'
2
+
3
+ module Arborist
4
+
5
+ config :migration do |c|
6
+ c.fallback = ModelReferenceError
7
+ c.default_method_name = :model
8
+ c.default_direction = :up
9
+ c.default_message = 'Migrating data...'
10
+ c.reset_column_information = true
11
+ end
12
+
13
+ class Migration < ActiveRecord::Migration
14
+ require_relative 'migration/collection'
15
+ require_relative 'migration/data'
16
+ require_relative 'migration/schema'
17
+
18
+ include Data
19
+ include Schema
20
+
21
+ class << self
22
+ attr_accessor :collection
23
+
24
+ def reset!
25
+ self.collection = Collection.new
26
+ end
27
+
28
+ private
29
+
30
+ def config
31
+ Arborist.config.migration
32
+ end
33
+ end
34
+
35
+ def exec_migration conn, dir
36
+ super conn, dir
37
+ collection[dir].each do |m|
38
+ m.report { instance_eval(&m.routine) }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def collection
45
+ self.class.collection
46
+ end
47
+
48
+ def config
49
+ self.class.config
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ require 'delegate'
2
+
3
+ class Arborist::Migration::Collection
4
+ extend Forwardable
5
+ def_delegators :@collection, :[], :fetch
6
+
7
+ def initialize
8
+ @collection = { up:[], down:[] }
9
+ end
10
+ end
@@ -0,0 +1,53 @@
1
+ module Arborist::Migration::Data
2
+ extend ActiveSupport::Concern
3
+
4
+ require_relative 'data_migration'
5
+ require_relative 'model_arguments'
6
+
7
+ Collection = Arborist::Migration::Collection
8
+ DataMigration = Arborist::Migration::DataMigration
9
+ ModelArguments = Arborist::Migration::ModelArguments
10
+
11
+ module ClassMethods
12
+ attr_accessor :model_ref
13
+
14
+ def data *args, &migration
15
+ data_migration = DataMigration.new *args, &migration
16
+
17
+ self.collection ||= Collection.new
18
+ self.collection[data_migration.direction] << data_migration
19
+ end
20
+
21
+ def model *args
22
+ model_args = ModelArguments.new args
23
+ define_model_reference model_args.model_ref
24
+ define_model_method model_args
25
+ end
26
+
27
+ private
28
+
29
+ def define_model_method model_args
30
+ define_method model_args.method_name do
31
+ @_ref ||= {}
32
+ @_ref[model_args] ||= begin
33
+ ref = self.class.model_ref.fetch model_args.model_ref
34
+
35
+ if Arborist.config.migration.reset_column_information
36
+ ref.tap(&:reset_column_information)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def define_model_reference model_ref
43
+ self.model_ref ||= {}
44
+ self.model_ref[model_ref] ||= Object.const_get model_ref
45
+ rescue NameError
46
+ config.fallback.new(model_ref).model
47
+ end
48
+
49
+ def config
50
+ Arborist.config.migration
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,23 @@
1
+ class Arborist::Migration::DataMigration
2
+ attr_reader :direction, :routine
3
+
4
+ def initialize *args, &block
5
+ @options = args.extract_options!
6
+ @direction = args.first || config.default_direction
7
+ @routine = @options[:use].new rescue block
8
+ end
9
+
10
+ def report &block
11
+ puts "~> #{config.default_message} #{options[:say]}"
12
+ time = Benchmark.measure(&block)
13
+ puts "~> Completed. Time elapsed: %.4fs" % time.real
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :options
19
+
20
+ def config
21
+ Arborist.config.migration
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ class Arborist::Migration::ModelArguments
2
+ attr_reader :model_ref, :method_name
3
+
4
+ def initialize args
5
+ options = args.extract_options!
6
+
7
+ @model_ref = args.first || model_from_options(options)
8
+ @method_name = options.fetch :as, config.default_method_name
9
+ end
10
+
11
+ private
12
+
13
+ RESERVED_OPTIONS = %i( as )
14
+
15
+ def model_from_options options
16
+ options
17
+ .select { |k, _| ! RESERVED_OPTIONS.include? k }
18
+ .values
19
+ .first
20
+ end
21
+
22
+ def config
23
+ Arborist.config.migration
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Arborist::Migration::Schema
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def schema method = :change, &migration
6
+ if SCHEMA_MIGRATION_METHODS.include? method
7
+ define_method method, &migration
8
+ else
9
+ raise Arborist::UnknownSchemaMethod, method
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ SCHEMA_MIGRATION_METHODS = %i( up down change )
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Arborist
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'arborist/configuration'
3
+
4
+ module Arborist
5
+ RSpec.describe Configuration do
6
+ context 'when setting a value' do
7
+ subject(:config) { described_class.new }
8
+
9
+ specify do
10
+ config.foo = :bar
11
+ expect(config.foo).to eq :bar
12
+ end
13
+ end
14
+
15
+ context 'when setting initial props' do
16
+ subject(:config) { described_class.new foo: :bar }
17
+ specify { expect(config.foo).to eq :bar }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+ require 'arborist/migration/collection'
3
+
4
+ describe Arborist::Migration::Collection do
5
+ subject(:collection) { described_class.new }
6
+
7
+ it { expect(collection.fetch :up).to eq [] }
8
+ it { expect(collection.fetch :down).to eq [] }
9
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'arborist/migration/model_arguments'
3
+
4
+ describe Arborist::Migration::ModelArguments do
5
+ subject(:model_args) { described_class.new args }
6
+
7
+ context 'when default' do
8
+ let(:args) { [:TestModel] }
9
+
10
+ it { expect(model_args.model_ref).to eq :TestModel }
11
+ it { expect(model_args.method_name).to eq :model }
12
+ end
13
+
14
+ context 'when declaring a method name' do
15
+ let(:args) { [:TestModel, { as: :test_method }] }
16
+
17
+ it { expect(model_args.model_ref).to eq :TestModel }
18
+ it { expect(model_args.method_name).to eq :test_method }
19
+ end
20
+
21
+ context 'when declaring a method name' do
22
+ let(:args) { [:TestModel, { as: :test_method }] }
23
+
24
+ it { expect(model_args.model_ref).to eq :TestModel }
25
+ it { expect(model_args.method_name).to eq :test_method }
26
+ end
27
+
28
+ context 'when a fallback is defined' do
29
+ let(:args) { [{ :Unknown => :TestModel }] }
30
+
31
+ it { expect(model_args.model_ref).to eq :TestModel }
32
+ end
33
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe Arborist::Migration do
4
+ describe 'public interface' do
5
+ %i( collection model_ref data model reset! ).each do |class_method|
6
+ it { expect(described_class).to respond_to class_method }
7
+ end
8
+ end
9
+
10
+ describe 'configuration' do
11
+ it 'sets up a migration container' do
12
+ expect(Arborist.config.migration).to be_a Arborist::Configuration
13
+ end
14
+ end
15
+
16
+ describe '.data' do
17
+ after { Arborist::Migration.reset! }
18
+
19
+ context 'without specifying a direction' do
20
+ it 'adds a migration routine to the :up collection' do
21
+ Arborist::Migration.data { :noop }
22
+ expect(Arborist::Migration.collection[:up].length).to eq 1
23
+ end
24
+ end
25
+
26
+ context 'when specifying a direction' do
27
+ it 'adds a migration routine to the appropriate collection' do
28
+ Arborist::Migration.data(:down) { :noop }
29
+
30
+ expect(Arborist::Migration.collection[:up].length).to eq 0
31
+ expect(Arborist::Migration.collection[:down].length).to eq 1
32
+ end
33
+ end
34
+
35
+ context 'when providing a data class' do
36
+ it 'delegates to the class' do
37
+ Arborist::Migration.data use: Proc
38
+
39
+ expect(Arborist::Migration.collection[:up].length).to eq 1
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '.schema' do
45
+ after { Arborist::Migration.reset! }
46
+
47
+ context 'when a migration method is passed in' do
48
+ before do
49
+ TestMigration.class_eval do
50
+ schema(:up) {}
51
+ end
52
+ end
53
+
54
+ it { expect(TestMigration.new).to respond_to :up }
55
+ end
56
+
57
+ context 'when a migration method being passed in does not exist' do
58
+ specify do
59
+ expect {
60
+ TestMigration.class_eval { schema(:foo) {} }
61
+ }.to raise_error Arborist::UnknownSchemaMethod
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '.model' do
67
+ context 'when the referenced model exists' do
68
+ before { Arborist::Migration.model :TestModel }
69
+
70
+ it 'defines a model reference via #model' do
71
+ expect(Arborist::Migration.new.model).to eq TestModel
72
+ end
73
+
74
+ it 'resets the column information' do
75
+ expect(Arborist::Migration.new.model).to eq TestModel
76
+ end
77
+ end
78
+
79
+ context 'when the model being referenced does not exist' do
80
+ specify do
81
+ expect { Arborist::Migration.model :UnknownModel }
82
+ .to raise_error Arborist::ModelReferenceError
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ describe '.reset!' do
89
+ it 'clears out the collection of migrations' do
90
+ Arborist::Migration.data { :noop }
91
+ expect(Arborist::Migration.collection[:up].length).to eq 1
92
+ Arborist::Migration.reset!
93
+ expect(Arborist::Migration.collection[:up].length).to eq 0
94
+ end
95
+ end
96
+ end
97
+
98
+ describe TestMigration do
99
+ before :all do
100
+ define_schema do
101
+ create_table(:test) { |t| t.timestamps null: false }
102
+ end
103
+
104
+ TestModel.create!
105
+ end
106
+
107
+ describe 'migrating up' do
108
+ it 'fills in the missing value' do
109
+ expect(TestModel.first).to_not respond_to :fullname
110
+
111
+ ActiveRecord::Migration.run TestMigration
112
+ expect(TestModel.first.fullname).to be_present
113
+
114
+ ActiveRecord::Migration.run SecondMigration
115
+ expect(TestModel.first.full_name).to be_present
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Arborist do
4
+ it 'has a version number' do
5
+ expect(Arborist::VERSION).not_to be nil
6
+ end
7
+
8
+ describe '.config' do
9
+ context 'without defining a namespace' do
10
+ it 'creates a blank container at the root level' do
11
+ expect(Arborist.config).to be_a Arborist::Configuration
12
+ end
13
+ end
14
+
15
+ context 'when defining a namespaces' do
16
+ it 'creates an empty container' do
17
+ Arborist.module_eval { config :test }
18
+ expect(Arborist.config.test).to be_a Arborist::Configuration
19
+ end
20
+ end
21
+
22
+ context 'when passing a block' do
23
+ it 'yields a configurable object' do
24
+ Arborist.module_eval do
25
+ config { |c| c.foo = :bar } # on the root
26
+ config(:test) { |c| c.foo2 = :bar2 } # namespaced
27
+ end
28
+
29
+ expect(Arborist.config.foo).to eq :bar
30
+ expect(Arborist.config.test.foo2).to eq :bar2
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'pry-byebug'
3
+ require 'arborist'
4
+
5
+ RSpec.configure do |c|
6
+ c.after(:all) { teardown_db }
7
+ end
8
+
9
+ ActiveRecord::Base.establish_connection(
10
+ adapter: "sqlite3",
11
+ database: ":memory:"
12
+ )
13
+
14
+ def define_schema(verbose = false, &schema)
15
+ ActiveRecord::Schema.verbose = verbose
16
+ ActiveRecord::Schema.define version: 1, &schema
17
+ end
18
+
19
+ def teardown_db
20
+ conn = ActiveRecord::Base.connection
21
+ conn.tables.each { |t| conn.drop_table t }
22
+ end
23
+
24
+ class TestModel < ActiveRecord::Base
25
+ self.table_name = :test
26
+ end
27
+
28
+ class TestMigration < Arborist::Migration
29
+ model :TestModel
30
+
31
+ data say: 'Filling in fullname' do
32
+ model.find_each do |tm|
33
+ tm.fullname = 'abc'
34
+ tm.save!
35
+ end
36
+ end
37
+
38
+ def change
39
+ add_column :test, :fullname, :string
40
+ end
41
+ end
42
+
43
+ class SecondMigration < Arborist::Migration
44
+ model :TestModel
45
+ data { :noop }
46
+
47
+ def change
48
+ rename_column :test, :fullname, :full_name
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arborist-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Cuppy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - adam@codingzeal.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rubocop.yml"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - arborist.gemspec
112
+ - lib/arborist.rb
113
+ - lib/arborist/configuration.rb
114
+ - lib/arborist/exceptions.rb
115
+ - lib/arborist/migration.rb
116
+ - lib/arborist/migration/collection.rb
117
+ - lib/arborist/migration/data.rb
118
+ - lib/arborist/migration/data_migration.rb
119
+ - lib/arborist/migration/model_arguments.rb
120
+ - lib/arborist/migration/schema.rb
121
+ - lib/arborist/version.rb
122
+ - spec/arborist/confirguration_spec.rb
123
+ - spec/arborist/migration/collection_spec.rb
124
+ - spec/arborist/migration/model_arguments_spec.rb
125
+ - spec/arborist/migration_spec.rb
126
+ - spec/arborist_spec.rb
127
+ - spec/spec_helper.rb
128
+ homepage: https://github.com/CodingZeal/arborist
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.2.2
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Framework for working with data migrations and seeds in a Rails application
152
+ test_files:
153
+ - spec/arborist/confirguration_spec.rb
154
+ - spec/arborist/migration/collection_spec.rb
155
+ - spec/arborist/migration/model_arguments_spec.rb
156
+ - spec/arborist/migration_spec.rb
157
+ - spec/arborist_spec.rb
158
+ - spec/spec_helper.rb
159
+ has_rdoc: