Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 3f28dfff0e7cefc2bf23a1767f363210f7bfefc3 0 parents
@pmenglund authored
19 .gitignore
@@ -0,0 +1,19 @@
+.idea
+.DS_Store
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
2  .rspec
@@ -0,0 +1,2 @@
+--color
+--format progress
13 Gemfile
@@ -0,0 +1,13 @@
+source 'https://rubygems.org'
+
+gemspec
+
+group :development, :test do
+ gem "rake"
+ gem "rspec"
+ gem "guard"
+ gem "guard-bundler"
+ gem "guard-rspec"
+ gem "rb-fsevent"
+ gem "terminal-notifier-guard"
+end
12 Guardfile
@@ -0,0 +1,12 @@
+guard 'bundler' do
+ watch('Gemfile')
+ watch(/^.+\.gemspec/)
+end
+
+guard 'rspec' do
+ watch(%r{^spec/unit/.+_spec\.rb$})
+ watch(%r{^spec/functional/.+_spec\.rb$})
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/functinoal/hash_migrations_spec.rb" }
+ watch('spec/spec_helper.rb') { "spec" }
+end
22 LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Martin Englund
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 README.md
@@ -0,0 +1,43 @@
+# Hash Migrations
+
+TODO: Write a gem description
+
+```
+Hash::Migrator.run(hash, dir, options)
+hash.migrate(dir, options)
+
+Hash.migration do
+ up do |hash|
+ hash[:foo] = hash['foo']
+ end
+
+ down do
+ end
+end
+```
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'hash-migrations'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install hash-migrations
+
+## Usage
+
+TODO: Write usage instructions here
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
1  Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
19 hash-migrations.gemspec
@@ -0,0 +1,19 @@
+# -*- encoding: utf-8 -*-
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'hash/migrations/version'
+
+Gem::Specification.new do |gem|
+ gem.name = "hash-migrations"
+ gem.version = Hash::Migrations::VERSION
+ gem.authors = ["Martin Englund"]
+ gem.email = ["martin@englund.nu"]
+ gem.description = %q{TODO: Write a gem description}
+ gem.summary = %q{TODO: Write a gem summary}
+ gem.homepage = ""
+
+ gem.files = `git ls-files`.split($/)
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.require_paths = ["lib"]
+end
15 lib/hash/migrations.rb
@@ -0,0 +1,15 @@
+require 'hash/migrations/version'
+require 'hash/migrations/errors'
+require 'hash/migrations/migration'
+require 'hash/migrations/migrator'
+require 'hash/migrations/core_ext'
+
+module Hash::Migrations
+ # returns a new hash with migrations run
+ def run(hash, dir, options={direction: :up})
+ hash.dup.migrate(dir, options)
+ end
+
+ module_function :run
+end
+
30 lib/hash/migrations/core_ext.rb
@@ -0,0 +1,30 @@
+module Hash::Migrations::InstanceMethods
+ def migrate(dir, options={direction: :up})
+ @direction = options[:direction]
+ Hash::Migrations::Migrator.new(dir).run(self)
+ end
+
+ def up(&block)
+ yield self if @direction == :up
+ rescue => e
+ raise Hash::Migrations::MigrationFailed.new(e)
+ end
+
+ def down(&block)
+ yield self if @direction == :down
+ end
+end
+
+class Hash
+ include Hash::Migrations::InstanceMethods
+end
+
+module Hash::Migrations::ClassMethods
+ def migration(&block)
+ block.call
+ end
+end
+
+class Hash
+ extend Hash::Migrations::ClassMethods
+end
4 lib/hash/migrations/errors.rb
@@ -0,0 +1,4 @@
+module Hash::Migrations
+ class MigrationFailed < StandardError
+ end
+end
19 lib/hash/migrations/migration.rb
@@ -0,0 +1,19 @@
+module Hash::Migrations
+ class Migration
+ attr_reader :migration
+
+ def initialize(arg)
+ if arg.is_a?(File)
+ @migration = File.read(arg)
+ elsif arg.is_a?(String)
+ @migration = arg
+ else
+ raise ArgumentError, "expected File or String, got #{arg.class}"
+ end
+ end
+
+ def to_str
+ migration
+ end
+ end
+end
33 lib/hash/migrations/migrator.rb
@@ -0,0 +1,33 @@
+module Hash::Migrations
+ class Migrator
+ attr_reader :migrations
+
+ def initialize(migrations_dir=nil)
+ @migrations = []
+
+ return unless migrations_dir
+
+ unless Dir.exists?(migrations_dir)
+ raise ArgumentError, "#{migrations_dir} is not a directory"
+ end
+
+ Dir.entries(migrations_dir).each do |entry|
+ unless entry.match(/^\.+$/)
+ file = File.new(File.join(migrations_dir, entry))
+ add(file)
+ end
+ end
+ end
+
+ def add(migration)
+ @migrations << Hash::Migrations::Migration.new(migration)
+ end
+
+ def run(hash)
+ migrations.each do |migration|
+ hash.instance_eval migration
+ end
+ hash
+ end
+ end
+end
3  lib/hash/migrations/version.rb
@@ -0,0 +1,3 @@
+module Hash::Migrations
+ VERSION = "0.0.1"
+end
5 spec/assets/fail/fail.rb
@@ -0,0 +1,5 @@
+Hash.migration do
+ up do |hash|
+ hash[:foo] = Array(hash[:foo][:bar])
+ end
+end
9 spec/assets/m1/m1.rb
@@ -0,0 +1,9 @@
+Hash.migration do
+ up do |hash|
+ hash[:foo] = Array(hash[:foo])
+ end
+
+ down do |hash|
+ hash[:foo] = hash[:foo].first
+ end
+end
5 spec/assets/m2/m2.rb
@@ -0,0 +1,5 @@
+Hash.migration do
+ up do |hash|
+ hash[:foo] = Array(hash[:foo])
+ end
+end
11 spec/assets/m3/m1.rb
@@ -0,0 +1,11 @@
+Hash.migration do
+ up do |hash|
+ hash.keys.each do |key|
+ if key.is_a?(String)
+ hash[key.to_sym] = hash[key]
+ hash.delete(key)
+ end
+ end
+ end
+end
+
6 spec/assets/m3/m2.rb
@@ -0,0 +1,6 @@
+Hash.migration do
+ up do |hash|
+ hash[:foo] = Array(hash[:foo])
+ end
+end
+
1  spec/assets/migrations/m1.rb
@@ -0,0 +1 @@
+m1.rb
1  spec/assets/migrations/m2.rb
@@ -0,0 +1 @@
+m2.rb
1  spec/assets/migrations/m3.rb
@@ -0,0 +1 @@
+m3.rb
49 spec/functional/hash_migrations_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Hash::Migrations do
+
+ context 'on hash' do
+ it 'should change self' do
+ hash = {:foo => 'bar'}
+
+ hash.migrate(asset('m1'))
+
+ hash.should == {:foo => %w[bar]}
+ end
+
+ it 'should run all migrations in the directory' do
+ hash = {'foo' => 'bar'}
+
+ hash.migrate(asset('m3'))
+
+ hash.should == {:foo => %w[bar]}
+
+ end
+
+ it 'should raise an error if a migration fails' do
+ expect {
+ {}.migrate(asset('fail'))
+ }.to raise_error Hash::Migrations::MigrationFailed
+ end
+ end
+
+ context 'on module' do
+ it 'should return a new hash' do
+ hash = {:foo => 'bar'}
+
+ migrated_hash = Hash::Migrations.run(hash, asset('m2'))
+
+ migrated_hash.should == {:foo => %w[bar]}
+ migrated_hash.should_not == hash
+ end
+
+ it 'should migrate a string to an array' do
+ migrator = Hash::Migrations::Migrator.new
+ migrator.add('hash[:foo] = Array(hash[:foo])')
+
+ migrated_hash = migrator.run({:foo => 'bar'})
+
+ migrated_hash.should == {:foo => %w[bar]}
+ end
+ end
+end
13 spec/spec_helper.rb
@@ -0,0 +1,13 @@
+require 'hash/migrations'
+
+def asset(file)
+ File.expand_path(File.join('..', 'assets', file), __FILE__)
+end
+
+RSpec.configure do |config|
+ config.treat_symbols_as_metadata_keys_with_true_values = true
+ config.run_all_when_everything_filtered = true
+ config.filter_run :focus
+
+ config.order = 'random'
+end
9 spec/unit/migraton_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe Hash::Migrations::Migration do
+ it 'should raise an error of the argument is neither a File nor a String' do
+ expect {
+ described_class.new(:foo)
+ }.to raise_error ArgumentError, 'expected File or String, got Symbol'
+ end
+end
9 spec/unit/migrator_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe Hash::Migrations::Migrator do
+ it 'should raise an error if the directory does not exist' do
+ expect {
+ described_class.new('/path/to/non/existing/directory')
+ }.to raise_error ArgumentError, '/path/to/non/existing/directory is not a directory'
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.