From 56e5e81e108aff28cb4258035dd767c73d3442a2 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 17:13:51 +0000 Subject: [PATCH 01/90] inital commit --- .gitignore | 17 +++++++++++++++++ Gemfile | 4 ++++ LICENSE.txt | 22 ++++++++++++++++++++++ README.md | 29 +++++++++++++++++++++++++++++ Rakefile | 1 + lib/morfo.rb | 5 +++++ lib/morfo/version.rb | 3 +++ morfo.gemspec | 23 +++++++++++++++++++++++ 8 files changed, 104 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 Rakefile create mode 100644 lib/morfo.rb create mode 100644 lib/morfo/version.rb create mode 100644 morfo.gemspec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d87d4be --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..784d92d --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in morfo.gemspec +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7358db7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2013 Leif Gensert + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..faf4289 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Morfo + +TODO: Write a gem description + +## Installation + +Add this line to your application's Gemfile: + + gem 'morfo' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install morfo + +## 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 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..2995527 --- /dev/null +++ b/Rakefile @@ -0,0 +1 @@ +require "bundler/gem_tasks" diff --git a/lib/morfo.rb b/lib/morfo.rb new file mode 100644 index 0000000..1340b4b --- /dev/null +++ b/lib/morfo.rb @@ -0,0 +1,5 @@ +require 'morfo/version' + +module Morfo + # Your code goes here... +end diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb new file mode 100644 index 0000000..48d71f5 --- /dev/null +++ b/lib/morfo/version.rb @@ -0,0 +1,3 @@ +module Morfo + VERSION = '0.0.1' +end diff --git a/morfo.gemspec b/morfo.gemspec new file mode 100644 index 0000000..5abece2 --- /dev/null +++ b/morfo.gemspec @@ -0,0 +1,23 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'morfo/version' + +Gem::Specification.new do |spec| + spec.name = 'morfo' + spec.version = Morfo::VERSION + spec.authors = ['Leif Gensert'] + spec.email = ['leifg@gmx.de'] + spec.description = %q{This gem provides a DSL for converting one hash into another} + spec.summary = %q{Inspired by ActiveImporter, this gem generically converts an array of hashes} + spec.homepage = '' + spec.license = 'MIT' + + spec.files = `git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ['lib'] + + spec.add_development_dependency 'bundler', '~> 1.3' + spec.add_development_dependency 'rake' +end From b2db84f10159f099733167893fdbb7910bf8844b Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 18:53:53 +0000 Subject: [PATCH 02/90] implement basic functionality --- .rspec | 1 + Gemfile | 12 ++++++ Guardfile | 8 ++++ lib/morfo.rb | 34 ++++++++++++++++- morfo.gemspec | 1 + spec/lib/morfo_spec.rb | 84 ++++++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 2 + 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 .rspec create mode 100644 Guardfile create mode 100644 spec/lib/morfo_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..6642bae --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--tty --color --format documentation \ No newline at end of file diff --git a/Gemfile b/Gemfile index 784d92d..5e13f29 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,15 @@ source 'https://rubygems.org' # Specify your gem's dependencies in morfo.gemspec gemspec + +group :test, :development do + gem 'guard' + gem 'guard-rspec' + gem 'simplecov' + gem 'pry' + + gem 'rb-inotify', require: false + gem 'rb-fsevent', require: false + gem 'rb-fchange', require: false + gem "codeclimate-test-reporter", require: false +end diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..85d551a --- /dev/null +++ b/Guardfile @@ -0,0 +1,8 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :rspec, all_on_start: true do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/lib/morfo.rb b/lib/morfo.rb index 1340b4b..4af4dbd 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,5 +1,37 @@ require 'morfo/version' module Morfo - # Your code goes here... + class Base + def self.map from, to, &transformation + mapping_actions << [from, to, transformation] + end + + def self.morf input + input.map do |value| + mapping_actions.inject({}) do |output, (from, to, transformation)| + resulting_value = apply_transformation( + extract_value(value, from), + transformation + ) + output.merge!(to => resulting_value) if resulting_value + output + end + end + end + + private + def self.extract_value value, from + Array(from).inject(value) do |resulting_value, key| + resulting_value[key] + end + end + + def self.apply_transformation value, transformation + transformation ? transformation.call(value) : value + end + + def self.mapping_actions + @actions ||= [] + end + end end diff --git a/morfo.gemspec b/morfo.gemspec index 5abece2..bb27eac 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -20,4 +20,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 1.3' spec.add_development_dependency 'rake' + spec.add_development_dependency 'rspec', '>= 2.14', '< 4.0' end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb new file mode 100644 index 0000000..d14cf93 --- /dev/null +++ b/spec/lib/morfo_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe Morfo::Base do + let(:input) do + [ + { + title: 'The Walking Dead', + channel: 'AMC', + watchers: 1337, + status: 'running', + cast: ['Lincoln, Andrew', 'McBride, Melissa'], + ratings: { + imdb: 8.7, + trakt: 89, + rotten_tomatoes: 93, + }, + }, + { + title: 'Breaking Bad', + channel: 'AMC', + watchers: 72891, + status: 'ended', + cast: ['Cranston, Bryan', 'Gunn, Anna'], + ratings: { + imdb: 9.5, + trakt: 95, + rotten_tomatoes: 100, + }, + } + ] + end + + describe '#morf' do + context '1 to 1 conversion' do + subject do + class TitleMapper < Morfo::Base + map :title, :tv_show_title + end + TitleMapper + end + + it 'maps title correctly' do + expected_output = input.map{|v| {tv_show_title: v[:title]} } + expect(subject.morf(input)).to eq(expected_output) + end + + it 'leaves out nil values in result' do + expected_output = [{},{}] + modified_input = input.map{|h| h.reject{|k, v| k == :title}} + expect(subject.morf(modified_input)).to eq(expected_output) + end + end + + context '1 to 1 conversion with transformation' do + subject do + class NumCastMapper < Morfo::Base + map :cast, :cast_num do |cast| + cast.size + end + end + NumCastMapper + end + + it 'calls transformation correctly' do + expected_output = input.map{|v| {cast_num: v[:cast].size} } + expect(subject.morf(input)).to eq(expected_output) + end + end + + context 'nested conversion' do + subject do + class ImdbRatingMapper < Morfo::Base + map [:ratings, :imdb], :rating + end + ImdbRatingMapper + end + + it 'maps nested attributes' do + expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } + expect(subject.morf(input)).to eq(expected_output) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..95f057d --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require 'rspec' +require 'morfo' From 65b3427b363e5d660dd600970ff3085fcb6dd048 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:36:33 +0000 Subject: [PATCH 03/90] rescue error for invalid paths --- lib/morfo.rb | 2 +- spec/lib/morfo_spec.rb | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 4af4dbd..45d2425 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -22,7 +22,7 @@ def self.morf input private def self.extract_value value, from Array(from).inject(value) do |resulting_value, key| - resulting_value[key] + resulting_value ? resulting_value[key] : nil end end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index d14cf93..723416d 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -68,16 +68,28 @@ class NumCastMapper < Morfo::Base end context 'nested conversion' do - subject do + subject(:valid_path) do class ImdbRatingMapper < Morfo::Base map [:ratings, :imdb], :rating end ImdbRatingMapper end + subject(:invalid_path) do + class InvalidImdbRatingMapper < Morfo::Base + map [:very, :long, :path, :that, :might, :not, :exist], :rating + end + InvalidImdbRatingMapper + end + it 'maps nested attributes' do expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } - expect(subject.morf(input)).to eq(expected_output) + expect(valid_path.morf(input)).to eq(expected_output) + end + + it 'doesn\'t raise error for invalid path' do + expected_output = [{},{}] + expect(invalid_path.morf(input)).to eq(expected_output) end end end From 4721e938e7cf68fc7567ee03dbd1a8f5e5b6c449 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:37:44 +0000 Subject: [PATCH 04/90] add README --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index faf4289..9caf3af 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Morfo -TODO: Write a gem description +This Gem is inspired by the [active_importer](https://github.com/continuum/active_importer) Gem. + +But instead of importing spreadsheets into models, you can morf (typo intended) arrays of Hashes into other arrays of hashes. ## Installation @@ -18,7 +20,74 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +In order to morf the hashes you have to provide a class that extends `Morf::Base` + +Use the `map` method to specify what field you map to another field: + + class TitleMorfer < Morfo::Base + map :title, :tv_show_title + end + +Afterwards use the `morf` method to morf all hashes in one array to the end result: + + Title.morf([ + {title: 'The Walking Dead'} , + {title: 'Breaking Bad'}, + ]) + + # [ + # {tv_show_title: 'The Walking Dead'}, + # {tv_show_title: 'Breaking Bad'}, + # ] + +## Transformations + +For each mapping you can define a block, that will be called on every input: + + class AndZombies < Morfo::Base + map :title, :title do |title| + "#{title} and Zombies" + end + end + + AndZombies.morf([ + {title: 'Pride and Prejudice'}, + {title: 'Fifty Shades of Grey'}, + ]) + + # [ + # {title: 'Pride and Prejudice and Zombies'}, + # {title: 'Fifty Shades of Grey and Zombies'}, + # ] + +## Nested Values + +You can directly access nested values in the hashes: + + class Name < Morfo::Base + map [:name, :firs], :first_name + map [:name, :last], :last_name + end + + Name.morf([ + { + name: { + first: 'Clark', + last: 'Kent', + }, + }, + { + name: { + first: 'Bruce', + last: 'Wayne', + }, + }, + ]) + + # [ + # {first_name: 'Clark',last_name: 'Kent'}, + # {first_name: 'Bruce',last_name: 'Wayne'},, + # ] ## Contributing From b76af42a5ad5e3ee812d876b70af0a73e76401c8 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:42:24 +0000 Subject: [PATCH 05/90] add disclaimer about compatibility --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9caf3af..cd645fc 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ This Gem is inspired by the [active_importer](https://github.com/continuum/activ But instead of importing spreadsheets into models, you can morf (typo intended) arrays of Hashes into other arrays of hashes. +## Compatibility + +This gem is currently only tested on Ruby 2.0 (including 2.0 mode of JRuby and RBX). + ## Installation Add this line to your application's Gemfile: From 493a6a4ca6112dbdab66d897148b59289a661886 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:45:51 +0000 Subject: [PATCH 06/90] make gem ready for Travis --- .travis.yml | 16 ++++++++++++++++ Rakefile | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..61de31a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: ruby +cache: bundler +env: JRUBY_OPTS=--2.0 +rvm: + - 2.0.0 + - jruby-1.7.9 + - ruby-head + - jruby-head + - rbx-2.2.1 +matrix: + allow_failures: + - rvm: ruby-head + - rvm: jruby-head + - rvm: rbx-2.2.1 +script: + - rake spec diff --git a/Rakefile b/Rakefile index 2995527..a81dfd8 100644 --- a/Rakefile +++ b/Rakefile @@ -1 +1,4 @@ -require "bundler/gem_tasks" +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) From 109bc9c945377c132fc16018b3025b322239649b Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:54:46 +0000 Subject: [PATCH 07/90] add coveralls --- Gemfile | 2 +- spec/spec_helper.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 5e13f29..c6d58e3 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source 'https://rubygems.org' gemspec group :test, :development do + gem 'coveralls', require: false gem 'guard' gem 'guard-rspec' gem 'simplecov' @@ -12,5 +13,4 @@ group :test, :development do gem 'rb-inotify', require: false gem 'rb-fsevent', require: false gem 'rb-fchange', require: false - gem "codeclimate-test-reporter", require: false end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 95f057d..39c452f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,10 @@ +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = Coveralls::SimpleCov::Formatter +SimpleCov.start do + add_filter 'spec' +end + require 'rspec' require 'morfo' From 2766a8267509d8dafbd7bcb42ffd8281af0665ab Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:57:24 +0000 Subject: [PATCH 08/90] add badges --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cd645fc..71dfd58 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Morfo +[![Build Status](https://travis-ci.org/leifg/morfo.png?branch=master)](https://travis-ci.org/leifg/morfo) [![Coverage Status](https://coveralls.io/repos/leifg/morfo/badge.png?branch=master)](https://coveralls.io/r/leifg/morfo) [![Code Climate](https://codeclimate.com/github/leifg/morfo.png)](https://codeclimate.com/github/leifg/morfo) [![Dependency Status](https://gemnasium.com/leifg/morfo.png)](https://gemnasium.com/leifg/morfo) [![Gem Version](https://badge.fury.io/rb/morfo.png)](http://badge.fury.io/rb/morfo) + This Gem is inspired by the [active_importer](https://github.com/continuum/active_importer) Gem. But instead of importing spreadsheets into models, you can morf (typo intended) arrays of Hashes into other arrays of hashes. From 6dfcbca4991e0c4d895a3c6bb2ca5ddf50d31add Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 19:59:03 +0000 Subject: [PATCH 09/90] fix travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 61de31a..401bc23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,4 @@ matrix: - rvm: jruby-head - rvm: rbx-2.2.1 script: - - rake spec + - bundle exec rake spec From 85e970ccf0bf6e740bb633131ac59772808a670a Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 20:05:41 +0000 Subject: [PATCH 10/90] add rake as a 'normal' dependency --- morfo.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/morfo.gemspec b/morfo.gemspec index bb27eac..b470f16 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -18,7 +18,8 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] + spec.add_dependency 'rake' + spec.add_development_dependency 'bundler', '~> 1.3' - spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '>= 2.14', '< 4.0' end From a3f388f58e360d97f634bfac3dd28d2b0754d6e8 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 20 Dec 2013 20:23:42 +0000 Subject: [PATCH 11/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 48d71f5..c4965ee 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = '0.0.1' + VERSION = '0.0.2' end From 62e2539c8f936d93bd0b2d8a91846aa13e46d429 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 22 Dec 2013 20:15:53 +0000 Subject: [PATCH 12/90] add test to verify multi mapping --- spec/lib/morfo_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 723416d..b142af1 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -67,6 +67,21 @@ class NumCastMapper < Morfo::Base end end + context '1 to many conversion' do + subject do + class MutliTitleMapper < Morfo::Base + map :title, :title + map :title, :also_title + end + MutliTitleMapper + end + + it 'maps title to multiple fields' do + expected_output = input.map{|v| {title: v[:title], also_title: v[:title]} } + expect(subject.morf(input)).to eq(expected_output) + end + end + context 'nested conversion' do subject(:valid_path) do class ImdbRatingMapper < Morfo::Base From bd389118574c790e02e32eddeeb76daff764d741 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 22 Dec 2013 20:17:17 +0000 Subject: [PATCH 13/90] name class in README consistently --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71dfd58..2d78fe4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ In order to morf the hashes you have to provide a class that extends `Morf::Base Use the `map` method to specify what field you map to another field: - class TitleMorfer < Morfo::Base + class Title < Morfo::Base map :title, :tv_show_title end From f6dfb3fb6bab75bb96613e615c6dc8cac850b31b Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 22 Dec 2013 20:18:47 +0000 Subject: [PATCH 14/90] add documentation for multi mapping --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 2d78fe4..4e6e01c 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,23 @@ Afterwards use the `morf` method to morf all hashes in one array to the end resu # {tv_show_title: 'Breaking Bad'}, # ] +It is also possible to map fields to multiple other fields + + class MultiTitle < Morfo::Base + map :title, :tv_show_title + map :title, :show_title + end + + MultiTitle.morf([ + {title: 'The Walking Dead'} , + {title: 'Breaking Bad'}, + ]) + + # [ + # {tv_show_title: 'The Walking Dead', show_title: 'The Walking Dead'}, + # {tv_show_title: 'Breaking Bad', show_title: 'Breaking Bad'}, + # ] + ## Transformations For each mapping you can define a block, that will be called on every input: From 49761354df4bbe85d8e84260f07199bd49b5a4b5 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 23 Dec 2013 02:05:26 +0100 Subject: [PATCH 15/90] internal changes for mapping --- lib/morfo.rb | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 45d2425..e47d355 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -3,35 +3,48 @@ module Morfo class Base def self.map from, to, &transformation - mapping_actions << [from, to, transformation] + mapping_actions << MapAction.new(from, to, transformation) end def self.morf input input.map do |value| - mapping_actions.inject({}) do |output, (from, to, transformation)| - resulting_value = apply_transformation( - extract_value(value, from), - transformation - ) - output.merge!(to => resulting_value) if resulting_value - output + mapping_actions.inject({}) do |output, action| + output.merge!(action.execute(value)) end end end private - def self.extract_value value, from + def self.mapping_actions + @actions ||= [] + end + end + + class MapAction + attr_reader :from + attr_reader :to + attr_reader :transformation + + def initialize from, to, transformation + @from = from + @to = to + @transformation = transformation + end + + def execute value + resulting_value = apply_transformation(extract_value(value)) + resulting_value ? { to => resulting_value } : {} + end + + private + def extract_value value Array(from).inject(value) do |resulting_value, key| resulting_value ? resulting_value[key] : nil end end - def self.apply_transformation value, transformation + def apply_transformation value transformation ? transformation.call(value) : value end - - def self.mapping_actions - @actions ||= [] - end end end From 7adbb33945cd0fd3dc3d4a0f527aa8bec45622bd Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 23 Dec 2013 11:09:28 +0100 Subject: [PATCH 16/90] name part of hash accordingly --- lib/morfo.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index e47d355..8848ffd 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -7,9 +7,9 @@ def self.map from, to, &transformation end def self.morf input - input.map do |value| + input.map do |row| mapping_actions.inject({}) do |output, action| - output.merge!(action.execute(value)) + output.merge!(action.execute(row)) end end end @@ -31,20 +31,20 @@ def initialize from, to, transformation @transformation = transformation end - def execute value - resulting_value = apply_transformation(extract_value(value)) + def execute row + resulting_value = apply_transformation(extract_value(row)) resulting_value ? { to => resulting_value } : {} end private - def extract_value value - Array(from).inject(value) do |resulting_value, key| + def extract_value row + Array(from).inject(row) do |resulting_value, key| resulting_value ? resulting_value[key] : nil end end - def apply_transformation value - transformation ? transformation.call(value) : value + def apply_transformation row + transformation ? transformation.call(row) : row end end end From 46a8372b7e2e80d1698db70da3e1c95de10aba91 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 16:23:33 +0100 Subject: [PATCH 17/90] one context more --- spec/lib/morfo_spec.rb | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index b142af1..2a78110 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -83,28 +83,30 @@ class MutliTitleMapper < Morfo::Base end context 'nested conversion' do - subject(:valid_path) do - class ImdbRatingMapper < Morfo::Base - map [:ratings, :imdb], :rating + context 'nested source' do + subject(:valid_path) do + class ImdbRatingMapper < Morfo::Base + map [:ratings, :imdb], :rating + end + ImdbRatingMapper end - ImdbRatingMapper - end - subject(:invalid_path) do - class InvalidImdbRatingMapper < Morfo::Base - map [:very, :long, :path, :that, :might, :not, :exist], :rating + subject(:invalid_path) do + class InvalidImdbRatingMapper < Morfo::Base + map [:very, :long, :path, :that, :might, :not, :exist], :rating + end + InvalidImdbRatingMapper end - InvalidImdbRatingMapper - end - it 'maps nested attributes' do - expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } - expect(valid_path.morf(input)).to eq(expected_output) - end + it 'maps nested attributes' do + expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } + expect(valid_path.morf(input)).to eq(expected_output) + end - it 'doesn\'t raise error for invalid path' do - expected_output = [{},{}] - expect(invalid_path.morf(input)).to eq(expected_output) + it 'doesn\'t raise error for invalid path' do + expected_output = [{},{}] + expect(invalid_path.morf(input)).to eq(expected_output) + end end end end From f1b4fd382756c73d4db7f5807e670cc3bd111912 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 16:57:14 +0100 Subject: [PATCH 18/90] implement nested storing --- lib/morfo.rb | 27 +++++++++++++++++++++++++-- spec/lib/morfo_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 8848ffd..e199d06 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -9,7 +9,7 @@ def self.map from, to, &transformation def self.morf input input.map do |row| mapping_actions.inject({}) do |output, action| - output.merge!(action.execute(row)) + deep_merge!(output, action.execute(row)) end end end @@ -18,6 +18,18 @@ def self.morf input def self.mapping_actions @actions ||= [] end + + def self.deep_merge! hash, other_hash, &block + other_hash.each_pair do |k,v| + tv = hash[k] + if tv.is_a?(Hash) && v.is_a?(Hash) + hash[k] = deep_merge!(tv, v, &block) + else + hash[k] = block && tv ? block.call(k, tv, v) : v + end + end + hash + end end class MapAction @@ -33,7 +45,7 @@ def initialize from, to, transformation def execute row resulting_value = apply_transformation(extract_value(row)) - resulting_value ? { to => resulting_value } : {} + resulting_value ? store_value(to, resulting_value) : {} end private @@ -46,5 +58,16 @@ def extract_value row def apply_transformation row transformation ? transformation.call(row) : row end + + def store_value to, value + Array(to).reverse.inject({}) do |hash, key| + if hash.keys.first.nil? + hash.merge!(key => value) + else + { key => hash } + end + end + #{ to => value } + end end end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 2a78110..3ccb982 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -108,6 +108,28 @@ class InvalidImdbRatingMapper < Morfo::Base expect(invalid_path.morf(input)).to eq(expected_output) end end + + context 'nested destination' do + subject do + class WrapperMapper < Morfo::Base + map :title, [:tv_show, :title] + map :channel, [:tv_show, :channel] + end + WrapperMapper + end + + it 'maps to nested destination' do + expected_output = input.map{|v| + { + tv_show: { + title: v[:title], + channel: v[:channel], + } + } + } + expect(subject.morf(input)).to eq(expected_output) + end + end end end end From cd323702509edc58a29621207c2bca897eec8cdf Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 17:07:39 +0100 Subject: [PATCH 19/90] typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e6e01c..cff349e 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ For each mapping you can define a block, that will be called on every input: You can directly access nested values in the hashes: class Name < Morfo::Base - map [:name, :firs], :first_name + map [:name, :first], :first_name map [:name, :last], :last_name end @@ -109,7 +109,7 @@ You can directly access nested values in the hashes: # [ # {first_name: 'Clark',last_name: 'Kent'}, - # {first_name: 'Bruce',last_name: 'Wayne'},, + # {first_name: 'Bruce',last_name: 'Wayne'}, # ] ## Contributing From cb0f7cd80ea5c0cfc6730be4bfb997883710f103 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 17:11:35 +0100 Subject: [PATCH 20/90] add nested storage to README --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index cff349e..199a6da 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,25 @@ You can directly access nested values in the hashes: # {first_name: 'Bruce',last_name: 'Wayne'}, # ] + +It is also possible to store values in a nested hash: + + class Wrapper < Morfo::Base + map :first_name, [:superhero, :name, :first] + map :last_name, [:superhero, :name, :last] + end + + Name.morf([ + {first_name: 'Clark',last_name: 'Kent'}, + {first_name: 'Bruce',last_name: 'Wayne'},, + ]) + + # [ + # { superhero: {name: { first: 'Clark', last: 'Kent'}}}, + # { superhero: {name: { first: 'Bruce', last: 'Wayne'}}}, + # ] + + ## Contributing 1. Fork it From 8cf8ddd5e1dabc413b8f6de4369bd7149b35fdc6 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 17:33:47 +0100 Subject: [PATCH 21/90] change title in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 199a6da..c1cb2dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Morfo +# El Morfo [![Build Status](https://travis-ci.org/leifg/morfo.png?branch=master)](https://travis-ci.org/leifg/morfo) [![Coverage Status](https://coveralls.io/repos/leifg/morfo/badge.png?branch=master)](https://coveralls.io/r/leifg/morfo) [![Code Climate](https://codeclimate.com/github/leifg/morfo.png)](https://codeclimate.com/github/leifg/morfo) [![Dependency Status](https://gemnasium.com/leifg/morfo.png)](https://gemnasium.com/leifg/morfo) [![Gem Version](https://badge.fury.io/rb/morfo.png)](http://badge.fury.io/rb/morfo) From a383ea486c2eec8ddcb12392e9a8f82be7439536 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 17:34:08 +0100 Subject: [PATCH 22/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index c4965ee..f63771c 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = '0.0.2' + VERSION = '0.0.3' end From b099a6cd6dd7ecbd1efe60fcd92dab402f6958b7 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sat, 11 Jan 2014 17:12:46 +0100 Subject: [PATCH 23/90] remove comment --- lib/morfo.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index e199d06..454d1d6 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -67,7 +67,6 @@ def store_value to, value { key => hash } end end - #{ to => value } end end end From a139bf7878f7a92a98a4a2e5871f729ee5687d7e Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 12 Jan 2014 16:12:47 +0100 Subject: [PATCH 24/90] add benchmark for simple mapping --- benchmarks/data.rb | 74 ++++++++++++++++++++++++++++++++++++++++++++++ benchmarks/run.rb | 46 ++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 benchmarks/data.rb create mode 100644 benchmarks/run.rb diff --git a/benchmarks/data.rb b/benchmarks/data.rb new file mode 100644 index 0000000..8a2e649 --- /dev/null +++ b/benchmarks/data.rb @@ -0,0 +1,74 @@ +module BenchmarkData + extend self + + def nested_wrapper + :person + end + + def row + { + first_name: 'Jazmyn', + last_name: 'Willms', + gender: 'female', + phone_number: '485-675-9228', + cell_phone: '1-172-435-9402 x4907', + street_name: 'Becker Inlet', + street_number: '15a', + city: 'Carolynchester', + zip: '38189', + country: 'USA', + } + end + + def row_string_keys + stringify_keys(row) + end + + def row_nested + { + nested_wrapper => row + } + end + + def row_nested_string_keys + { + nested_wrapper.to_s => row_string_keys + } + end + + private + def stringify_keys hash + hash.keys.each do |key| + hash[key.to_s] = hash.delete(key) + end + hash + end + + class SimpleMappingSymbol < Morfo::Base + BenchmarkData.row.keys.each do |field| + map field, :"#{field}_mapped" + end + end + + class SimpleMappingString < Morfo::Base + BenchmarkData.row_string_keys.keys.each do |field| + map field, "#{field}_mapped" + end + end + + class NestedMappingSymbol < Morfo::Base + BenchmarkData.row_nested.each do |key, value| + value.keys.each do |field| + map [key, field], :"#{field}_mapped" + end + end + end + + class NestedMappingString < Morfo::Base + BenchmarkData.row_nested_string_keys.each do |key, value| + value.keys.each do |field| + map [key, field], "#{field}_mapped" + end + end + end +end diff --git a/benchmarks/run.rb b/benchmarks/run.rb new file mode 100644 index 0000000..138f087 --- /dev/null +++ b/benchmarks/run.rb @@ -0,0 +1,46 @@ +require 'morfo' +require 'benchmark' +require './benchmarks/data' + +iterations = 100 +batch_size = 10000 + +definitions = [ + { + label: 'Simple (strings)', + row: BenchmarkData.row_string_keys, + morf_class: BenchmarkData::SimpleMappingString + }, + { + label: 'Simple (symbols)', + row: BenchmarkData.row, + morf_class: BenchmarkData::SimpleMappingSymbol + }, + { + label: 'Nested (strings)', + row: BenchmarkData.row_nested_string_keys, + morf_class: BenchmarkData::NestedMappingString + }, + { + label: 'Nested (symbols)', + row: BenchmarkData.row_nested, + morf_class: BenchmarkData::NestedMappingSymbol + }, +] + +definitions.each do |defintition| + defintition.merge!( + data: Array.new(batch_size){ defintition[:row] } + ) +end + +Benchmark.bm(20) do |x| + definitions.each do |defintition| + x.report(defintition[:label]) do + iterations.times do + defintition[:morf_class].morf(defintition[:data]) + end + end + end +end + From 4f4cd59d55160cdc0ee73ac53d4bd852848b1ff1 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 12 Jan 2014 23:41:49 +0100 Subject: [PATCH 25/90] change interface to declarative --- lib/morfo.rb | 4 ++-- spec/lib/morfo_spec.rb | 18 ++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 454d1d6..84a4503 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -2,8 +2,8 @@ module Morfo class Base - def self.map from, to, &transformation - mapping_actions << MapAction.new(from, to, transformation) + def self.field field_name, definition + mapping_actions << MapAction.new(definition[:from], field_name, definition[:transformation]) end def self.morf input diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 3ccb982..41018c4 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -34,7 +34,7 @@ context '1 to 1 conversion' do subject do class TitleMapper < Morfo::Base - map :title, :tv_show_title + field :tv_show_title, from: :title end TitleMapper end @@ -54,9 +54,7 @@ class TitleMapper < Morfo::Base context '1 to 1 conversion with transformation' do subject do class NumCastMapper < Morfo::Base - map :cast, :cast_num do |cast| - cast.size - end + field :cast_num, from: :cast, transformation: proc { |v| v.size} end NumCastMapper end @@ -70,8 +68,8 @@ class NumCastMapper < Morfo::Base context '1 to many conversion' do subject do class MutliTitleMapper < Morfo::Base - map :title, :title - map :title, :also_title + field :title, from: :title + field :also_title, from: :title end MutliTitleMapper end @@ -86,14 +84,14 @@ class MutliTitleMapper < Morfo::Base context 'nested source' do subject(:valid_path) do class ImdbRatingMapper < Morfo::Base - map [:ratings, :imdb], :rating + field :rating, from: [:ratings, :imdb] end ImdbRatingMapper end subject(:invalid_path) do class InvalidImdbRatingMapper < Morfo::Base - map [:very, :long, :path, :that, :might, :not, :exist], :rating + field :rating, from: [:very, :long, :path, :that, :might, :not, :exist] end InvalidImdbRatingMapper end @@ -112,8 +110,8 @@ class InvalidImdbRatingMapper < Morfo::Base context 'nested destination' do subject do class WrapperMapper < Morfo::Base - map :title, [:tv_show, :title] - map :channel, [:tv_show, :channel] + field([:tv_show, :title], from: :title) + field([:tv_show, :channel], from: :channel) end WrapperMapper end From 8ff8058ebfe80b5a5078a187aca5878250566d36 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 12 Jan 2014 23:51:47 +0100 Subject: [PATCH 26/90] adapt benchmarks --- benchmarks/data.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmarks/data.rb b/benchmarks/data.rb index 8a2e649..520be32 100644 --- a/benchmarks/data.rb +++ b/benchmarks/data.rb @@ -46,20 +46,20 @@ def stringify_keys hash class SimpleMappingSymbol < Morfo::Base BenchmarkData.row.keys.each do |field| - map field, :"#{field}_mapped" + field(:"#{field}_mapped", from: field) end end class SimpleMappingString < Morfo::Base BenchmarkData.row_string_keys.keys.each do |field| - map field, "#{field}_mapped" + field("#{field}_mapped", from: field) end end class NestedMappingSymbol < Morfo::Base BenchmarkData.row_nested.each do |key, value| value.keys.each do |field| - map [key, field], :"#{field}_mapped" + field(:"#{field}_mapped", from: [key, field]) end end end @@ -67,7 +67,7 @@ class NestedMappingSymbol < Morfo::Base class NestedMappingString < Morfo::Base BenchmarkData.row_nested_string_keys.each do |key, value| value.keys.each do |field| - map [key, field], "#{field}_mapped" + field("#{field}_mapped", from: [key, field]) end end end From 6a720d0abc1f95f7448a27170e2b8f26190616fe Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Sun, 12 Jan 2014 23:58:20 +0100 Subject: [PATCH 27/90] add validation for field --- lib/morfo.rb | 4 ++++ spec/lib/morfo_spec.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/morfo.rb b/lib/morfo.rb index 84a4503..d90cef1 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -3,6 +3,10 @@ module Morfo class Base def self.field field_name, definition + raise( + ArgumentError, + "No field to map from is specified for #{field_name.inspect}" + ) unless definition[:from] mapping_actions << MapAction.new(definition[:from], field_name, definition[:transformation]) end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 41018c4..3dd1ca1 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -31,6 +31,18 @@ end describe '#morf' do + context 'errors' do + subject(:no_from) do + class NilMapper < Morfo::Base + field :my_field, {} + end + NilMapper + end + it 'raises error for nil field' do + expect{no_from.morf([])}.to raise_error(ArgumentError) + end + end + context '1 to 1 conversion' do subject do class TitleMapper < Morfo::Base From 892cc1f935d7510c321b9e60af0e8625dbd43fd1 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 00:15:08 +0100 Subject: [PATCH 28/90] implement calculations --- lib/morfo.rb | 28 +++++++++++++++++++++++----- spec/lib/morfo_spec.rb | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index d90cef1..cb7e81d 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -3,11 +3,15 @@ module Morfo class Base def self.field field_name, definition - raise( - ArgumentError, - "No field to map from is specified for #{field_name.inspect}" - ) unless definition[:from] - mapping_actions << MapAction.new(definition[:from], field_name, definition[:transformation]) + if definition[:calculation] + mapping_actions << TransformationAction.new(field_name, definition[:calculation]) + else + raise( + ArgumentError, + "No field to map from is specified for #{field_name.inspect}" + ) unless definition[:from] + mapping_actions << MapAction.new(definition[:from], field_name, definition[:transformation]) + end end def self.morf input @@ -36,6 +40,20 @@ def self.deep_merge! hash, other_hash, &block end end + class TransformationAction + attr_reader :to + attr_reader :calculation + + def initialize to, calculation + @to = to + @calculation = calculation + end + + def execute row + {to => calculation.call(row)} + end + end + class MapAction attr_reader :from attr_reader :to diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 3dd1ca1..b00e74b 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -141,5 +141,23 @@ class WrapperMapper < Morfo::Base end end end + + context 'calculations' do + subject do + class TitlePrefixMapper < Morfo::Base + field :title_with_channel, calculation: proc{|v| "#{v[:title]}, (#{v[:channel]})"} + end + TitlePrefixMapper + end + + it 'maps calculation correctly' do + expected_output = input.map{|v| + { + title_with_channel: "#{v[:title]}, (#{v[:channel]})" + } + } + expect(subject.morf(input)).to eq(expected_output) + end + end end end From 786b453b1365a47448b47379be0a4837ca976808 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 11:03:50 +0100 Subject: [PATCH 29/90] change interface for calculations and transformations --- lib/morfo.rb | 61 ++++----------------------------------- lib/morfo/actions.rb | 56 +++++++++++++++++++++++++++++++++++ lib/morfo/actions/base.rb | 0 spec/lib/morfo_spec.rb | 24 +++++++++++---- 4 files changed, 79 insertions(+), 62 deletions(-) create mode 100644 lib/morfo/actions.rb create mode 100644 lib/morfo/actions/base.rb diff --git a/lib/morfo.rb b/lib/morfo.rb index cb7e81d..538dbba 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,16 +1,17 @@ require 'morfo/version' +require 'morfo/actions' module Morfo class Base - def self.field field_name, definition - if definition[:calculation] - mapping_actions << TransformationAction.new(field_name, definition[:calculation]) + def self.field field_name, definition={}, &blk + if blk + mapping_actions << Morfo::Actions::TransformationAction.new(definition[:from], field_name, blk) else raise( ArgumentError, "No field to map from is specified for #{field_name.inspect}" ) unless definition[:from] - mapping_actions << MapAction.new(definition[:from], field_name, definition[:transformation]) + mapping_actions << Morfo::Actions::MapAction.new(definition[:from], field_name) end end @@ -39,56 +40,4 @@ def self.deep_merge! hash, other_hash, &block hash end end - - class TransformationAction - attr_reader :to - attr_reader :calculation - - def initialize to, calculation - @to = to - @calculation = calculation - end - - def execute row - {to => calculation.call(row)} - end - end - - class MapAction - attr_reader :from - attr_reader :to - attr_reader :transformation - - def initialize from, to, transformation - @from = from - @to = to - @transformation = transformation - end - - def execute row - resulting_value = apply_transformation(extract_value(row)) - resulting_value ? store_value(to, resulting_value) : {} - end - - private - def extract_value row - Array(from).inject(row) do |resulting_value, key| - resulting_value ? resulting_value[key] : nil - end - end - - def apply_transformation row - transformation ? transformation.call(row) : row - end - - def store_value to, value - Array(to).reverse.inject({}) do |hash, key| - if hash.keys.first.nil? - hash.merge!(key => value) - else - { key => hash } - end - end - end - end end diff --git a/lib/morfo/actions.rb b/lib/morfo/actions.rb new file mode 100644 index 0000000..d7484e8 --- /dev/null +++ b/lib/morfo/actions.rb @@ -0,0 +1,56 @@ +module Morfo + module Actions + module ValueMethods + def extract_value from, row + Array(from).inject(row) do |resulting_value, key| + resulting_value ? resulting_value[key] : nil + end + end + + def store_value to, value + return {} if value.nil? + + Array(to).reverse.inject({}) do |hash, key| + if hash.keys.first.nil? + hash.merge!(key => value) + else + { key => hash } + end + end + end + end + + class MapAction + include ValueMethods + attr_reader :from + attr_reader :to + + def initialize from, to + @from = from + @to = to + end + + def execute row + store_value(to, extract_value(from, row)) + end + end + + class TransformationAction + include ValueMethods + attr_reader :to + attr_reader :from + attr_reader :transformation + + def initialize from, to, transformation + @from = from + @to = to + @transformation = transformation + end + + def execute row + resulting_value = from ? extract_value(from, row) : nil + store_value(to, transformation.call(resulting_value,row)) + end + end + end +end \ No newline at end of file diff --git a/lib/morfo/actions/base.rb b/lib/morfo/actions/base.rb new file mode 100644 index 0000000..e69de29 diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index b00e74b..a45142b 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -66,7 +66,7 @@ class TitleMapper < Morfo::Base context '1 to 1 conversion with transformation' do subject do class NumCastMapper < Morfo::Base - field :cast_num, from: :cast, transformation: proc { |v| v.size} + field(:cast_num, from: :cast){|v,r| v.size} end NumCastMapper end @@ -101,6 +101,13 @@ class ImdbRatingMapper < Morfo::Base ImdbRatingMapper end + subject(:valid_path_with_transformation) do + class ImdbRatingMapper < Morfo::Base + field(:rating, from: [:ratings, :imdb]){|v| "Rating: #{v}"} + end + ImdbRatingMapper + end + subject(:invalid_path) do class InvalidImdbRatingMapper < Morfo::Base field :rating, from: [:very, :long, :path, :that, :might, :not, :exist] @@ -113,6 +120,11 @@ class InvalidImdbRatingMapper < Morfo::Base expect(valid_path.morf(input)).to eq(expected_output) end + it 'maps nested attributes with transformation' do + expected_output = input.map{|v| {rating: "Rating: #{v[:ratings][:imdb]}"} } + expect(valid_path_with_transformation.morf(input)).to eq(expected_output) + end + it 'doesn\'t raise error for invalid path' do expected_output = [{},{}] expect(invalid_path.morf(input)).to eq(expected_output) @@ -123,7 +135,7 @@ class InvalidImdbRatingMapper < Morfo::Base subject do class WrapperMapper < Morfo::Base field([:tv_show, :title], from: :title) - field([:tv_show, :channel], from: :channel) + field([:tv_show, :channel], from: :channel){|v| "Channel: #{v}"} end WrapperMapper end @@ -133,7 +145,7 @@ class WrapperMapper < Morfo::Base { tv_show: { title: v[:title], - channel: v[:channel], + channel: "Channel: #{v[:channel]}", } } } @@ -145,15 +157,15 @@ class WrapperMapper < Morfo::Base context 'calculations' do subject do class TitlePrefixMapper < Morfo::Base - field :title_with_channel, calculation: proc{|v| "#{v[:title]}, (#{v[:channel]})"} + field(:title_with_channel){|v,r| "#{r[:title]}, (#{r[:channel]})"} end TitlePrefixMapper end it 'maps calculation correctly' do - expected_output = input.map{|v| + expected_output = input.map{|r| { - title_with_channel: "#{v[:title]}, (#{v[:channel]})" + title_with_channel: "#{r[:title]}, (#{r[:channel]})" } } expect(subject.morf(input)).to eq(expected_output) From b4f7182dc679480792d239a6421ce87c35952b66 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 11:18:24 +0100 Subject: [PATCH 30/90] add test for static value --- spec/lib/morfo_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index a45142b..e92854f 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -171,5 +171,19 @@ class TitlePrefixMapper < Morfo::Base expect(subject.morf(input)).to eq(expected_output) end end + + context 'static values' do + subject do + class StaticTitleMapper < Morfo::Base + field(:new_title){ 'Static Title' } + end + StaticTitleMapper + end + + it 'maps static value correctly' do + expected_output = input.map{|r| {new_title: 'Static Title'} } + expect(subject.morf(input)).to eq(expected_output) + end + end end end From f9e7ea82ee250188aa59fce309bc04dcf1487eec Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 11:46:02 +0100 Subject: [PATCH 31/90] change README to new syntax --- README.md | 93 ++++++++++++++++++++++--------------------------------- 1 file changed, 37 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index c1cb2dd..6203126 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ [![Build Status](https://travis-ci.org/leifg/morfo.png?branch=master)](https://travis-ci.org/leifg/morfo) [![Coverage Status](https://coveralls.io/repos/leifg/morfo/badge.png?branch=master)](https://coveralls.io/r/leifg/morfo) [![Code Climate](https://codeclimate.com/github/leifg/morfo.png)](https://codeclimate.com/github/leifg/morfo) [![Dependency Status](https://gemnasium.com/leifg/morfo.png)](https://gemnasium.com/leifg/morfo) [![Gem Version](https://badge.fury.io/rb/morfo.png)](http://badge.fury.io/rb/morfo) -This Gem is inspired by the [active_importer](https://github.com/continuum/active_importer) Gem. - -But instead of importing spreadsheets into models, you can morf (typo intended) arrays of Hashes into other arrays of hashes. +This gem acts like a universal converter from hashes into other hashes. You just define where your hash should get its data from and morfo will do the rest for you. ## Compatibility @@ -28,10 +26,14 @@ Or install it yourself as: In order to morf the hashes you have to provide a class that extends `Morf::Base` -Use the `map` method to specify what field you map to another field: +Use the `field` method to specify what fields exist and where they will get their data from: + +### Simple Mapping + +The most basic form is, just define another field from the input hash. The value will just be copied. class Title < Morfo::Base - map :title, :tv_show_title + field :tv_show_title, from: :title end Afterwards use the `morf` method to morf all hashes in one array to the end result: @@ -46,50 +48,12 @@ Afterwards use the `morf` method to morf all hashes in one array to the end resu # {tv_show_title: 'Breaking Bad'}, # ] -It is also possible to map fields to multiple other fields - - class MultiTitle < Morfo::Base - map :title, :tv_show_title - map :title, :show_title - end - - MultiTitle.morf([ - {title: 'The Walking Dead'} , - {title: 'Breaking Bad'}, - ]) - - # [ - # {tv_show_title: 'The Walking Dead', show_title: 'The Walking Dead'}, - # {tv_show_title: 'Breaking Bad', show_title: 'Breaking Bad'}, - # ] - -## Transformations - -For each mapping you can define a block, that will be called on every input: - - class AndZombies < Morfo::Base - map :title, :title do |title| - "#{title} and Zombies" - end - end - - AndZombies.morf([ - {title: 'Pride and Prejudice'}, - {title: 'Fifty Shades of Grey'}, - ]) - - # [ - # {title: 'Pride and Prejudice and Zombies'}, - # {title: 'Fifty Shades of Grey and Zombies'}, - # ] - -## Nested Values +If you want to have access to nested values, you'll have to provide an array as the key: -You can directly access nested values in the hashes: class Name < Morfo::Base - map [:name, :first], :first_name - map [:name, :last], :last_name + field :first_name, from: [:name, :first] + field :last_name, from: [:name, :last] end Name.morf([ @@ -112,22 +76,39 @@ You can directly access nested values in the hashes: # {first_name: 'Bruce',last_name: 'Wayne'}, # ] +## Transformations -It is also possible to store values in a nested hash: +Every field can also take a transformation block, so that the original input can be transformed. - class Wrapper < Morfo::Base - map :first_name, [:superhero, :name, :first] - map :last_name, [:superhero, :name, :last] + class AndZombies < Morfo::Base + field(:title, from: :title) {|title| "#{title} and Zombies"} end - Name.morf([ - {first_name: 'Clark',last_name: 'Kent'}, - {first_name: 'Bruce',last_name: 'Wayne'},, - ]) + AndZombies.morf([ + {title: 'Pride and Prejudice'}, + {title: 'Fifty Shades of Grey'}, + ]) + + # [ + # {title: 'Pride and Prejudice and Zombies'}, + # {title: 'Fifty Shades of Grey and Zombies'}, + # ] + +As the second argument, the whole row is passed into the block. So you can even do transformation based on the whole row. Or you can leave out all the arguments and return a static value. + + class NameConcatenator < Morfo::Base + field(:name) {|_, row| "#{row[:first_name]} #{row[:last_name]}"} + field(:status) { 'Best Friend' } + end + + NameConcatenator.morf([ + {first_name: 'Robin', last_name: 'Hood'}, + {first_name: 'Sherlock', last_name: 'Holmes'}, + ]) # [ - # { superhero: {name: { first: 'Clark', last: 'Kent'}}}, - # { superhero: {name: { first: 'Bruce', last: 'Wayne'}}}, + # {:name=>"Robin Hood", :status=>"Best Friend"}, + # {:name=>"Sherlock Holmes", :status=>'Best Friend'} # ] From 6d87ce9eb24edc94fdff248f09205b27bcf3a3ac Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 12:10:03 +0100 Subject: [PATCH 32/90] rename stubs in specs --- spec/lib/morfo_spec.rb | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index e92854f..a90497c 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -33,10 +33,10 @@ describe '#morf' do context 'errors' do subject(:no_from) do - class NilMapper < Morfo::Base + class NilMorfer < Morfo::Base field :my_field, {} end - NilMapper + NilMorfer end it 'raises error for nil field' do expect{no_from.morf([])}.to raise_error(ArgumentError) @@ -45,10 +45,10 @@ class NilMapper < Morfo::Base context '1 to 1 conversion' do subject do - class TitleMapper < Morfo::Base + class TitleMorfer < Morfo::Base field :tv_show_title, from: :title end - TitleMapper + TitleMorfer end it 'maps title correctly' do @@ -65,10 +65,10 @@ class TitleMapper < Morfo::Base context '1 to 1 conversion with transformation' do subject do - class NumCastMapper < Morfo::Base + class NumCastMorfer < Morfo::Base field(:cast_num, from: :cast){|v,r| v.size} end - NumCastMapper + NumCastMorfer end it 'calls transformation correctly' do @@ -79,11 +79,11 @@ class NumCastMapper < Morfo::Base context '1 to many conversion' do subject do - class MutliTitleMapper < Morfo::Base + class MutliTitleMorfer < Morfo::Base field :title, from: :title field :also_title, from: :title end - MutliTitleMapper + MutliTitleMorfer end it 'maps title to multiple fields' do @@ -95,24 +95,24 @@ class MutliTitleMapper < Morfo::Base context 'nested conversion' do context 'nested source' do subject(:valid_path) do - class ImdbRatingMapper < Morfo::Base + class ImdbRatingMorfer < Morfo::Base field :rating, from: [:ratings, :imdb] end - ImdbRatingMapper + ImdbRatingMorfer end subject(:valid_path_with_transformation) do - class ImdbRatingMapper < Morfo::Base + class ImdbRatingMorfer < Morfo::Base field(:rating, from: [:ratings, :imdb]){|v| "Rating: #{v}"} end - ImdbRatingMapper + ImdbRatingMorfer end subject(:invalid_path) do - class InvalidImdbRatingMapper < Morfo::Base + class InvalidImdbRatingMorfer < Morfo::Base field :rating, from: [:very, :long, :path, :that, :might, :not, :exist] end - InvalidImdbRatingMapper + InvalidImdbRatingMorfer end it 'maps nested attributes' do @@ -133,11 +133,11 @@ class InvalidImdbRatingMapper < Morfo::Base context 'nested destination' do subject do - class WrapperMapper < Morfo::Base + class WrapperMorfer < Morfo::Base field([:tv_show, :title], from: :title) field([:tv_show, :channel], from: :channel){|v| "Channel: #{v}"} end - WrapperMapper + WrapperMorfer end it 'maps to nested destination' do @@ -156,10 +156,10 @@ class WrapperMapper < Morfo::Base context 'calculations' do subject do - class TitlePrefixMapper < Morfo::Base + class TitlePrefixMorfer < Morfo::Base field(:title_with_channel){|v,r| "#{r[:title]}, (#{r[:channel]})"} end - TitlePrefixMapper + TitlePrefixMorfer end it 'maps calculation correctly' do @@ -174,10 +174,10 @@ class TitlePrefixMapper < Morfo::Base context 'static values' do subject do - class StaticTitleMapper < Morfo::Base + class StaticTitleMorfer < Morfo::Base field(:new_title){ 'Static Title' } end - StaticTitleMapper + StaticTitleMorfer end it 'maps static value correctly' do From fa5ca9db96e28cdbfaf0d397b155fd22e25f492b Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 12:42:55 +0100 Subject: [PATCH 33/90] change name in benchmarks --- benchmarks/data.rb | 8 ++++---- benchmarks/run.rb | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/benchmarks/data.rb b/benchmarks/data.rb index 520be32..d55217e 100644 --- a/benchmarks/data.rb +++ b/benchmarks/data.rb @@ -44,19 +44,19 @@ def stringify_keys hash hash end - class SimpleMappingSymbol < Morfo::Base + class SimpleMorferSymbol < Morfo::Base BenchmarkData.row.keys.each do |field| field(:"#{field}_mapped", from: field) end end - class SimpleMappingString < Morfo::Base + class SimpleMorferString < Morfo::Base BenchmarkData.row_string_keys.keys.each do |field| field("#{field}_mapped", from: field) end end - class NestedMappingSymbol < Morfo::Base + class NestedMorferSymbol < Morfo::Base BenchmarkData.row_nested.each do |key, value| value.keys.each do |field| field(:"#{field}_mapped", from: [key, field]) @@ -64,7 +64,7 @@ class NestedMappingSymbol < Morfo::Base end end - class NestedMappingString < Morfo::Base + class NestedMorferString < Morfo::Base BenchmarkData.row_nested_string_keys.each do |key, value| value.keys.each do |field| field("#{field}_mapped", from: [key, field]) diff --git a/benchmarks/run.rb b/benchmarks/run.rb index 138f087..e0ea8e9 100644 --- a/benchmarks/run.rb +++ b/benchmarks/run.rb @@ -9,22 +9,22 @@ { label: 'Simple (strings)', row: BenchmarkData.row_string_keys, - morf_class: BenchmarkData::SimpleMappingString + morf_class: BenchmarkData::SimpleMorferString }, { label: 'Simple (symbols)', row: BenchmarkData.row, - morf_class: BenchmarkData::SimpleMappingSymbol + morf_class: BenchmarkData::SimpleMorferSymbol }, { label: 'Nested (strings)', row: BenchmarkData.row_nested_string_keys, - morf_class: BenchmarkData::NestedMappingString + morf_class: BenchmarkData::NestedMorferString }, { label: 'Nested (symbols)', row: BenchmarkData.row_nested, - morf_class: BenchmarkData::NestedMappingSymbol + morf_class: BenchmarkData::NestedMorferSymbol }, ] From e5e2f644cc5b9b3c99fdbb752c11bfab20903790 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 12:43:25 +0100 Subject: [PATCH 34/90] remove map from error message --- lib/morfo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 538dbba..c6ac8f9 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -9,7 +9,7 @@ def self.field field_name, definition={}, &blk else raise( ArgumentError, - "No field to map from is specified for #{field_name.inspect}" + "No field to get value from is specified for #{field_name.inspect}" ) unless definition[:from] mapping_actions << Morfo::Actions::MapAction.new(definition[:from], field_name) end From f73612fbc10a35027a06ae948b47c4384185159f Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 00:30:39 +0100 Subject: [PATCH 35/90] remove empty file --- lib/morfo/actions/base.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lib/morfo/actions/base.rb diff --git a/lib/morfo/actions/base.rb b/lib/morfo/actions/base.rb deleted file mode 100644 index e69de29..0000000 From b09c669676300b7c3f659cd60cc6650a0d4c3f6f Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 00:30:50 +0100 Subject: [PATCH 36/90] change syntax (again) --- lib/morfo.rb | 38 +++++++++------ lib/morfo/actions.rb | 105 +++++++++++++++++++++++++++-------------- spec/lib/morfo_spec.rb | 32 ++++++------- 3 files changed, 109 insertions(+), 66 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index c6ac8f9..168d01a 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -3,29 +3,37 @@ module Morfo class Base - def self.field field_name, definition={}, &blk - if blk - mapping_actions << Morfo::Actions::TransformationAction.new(definition[:from], field_name, blk) - else - raise( - ArgumentError, - "No field to get value from is specified for #{field_name.inspect}" - ) unless definition[:from] - mapping_actions << Morfo::Actions::MapAction.new(definition[:from], field_name) - end + def self.field *field_path + act = Morfo::Actions::Field.new(field_path, mapping_actions) + mapping_actions[field_path] = act + act end def self.morf input - input.map do |row| - mapping_actions.inject({}) do |output, action| - deep_merge!(output, action.execute(row)) + input.map {|row| + output_row = {} + mapping_actions.each do |field_path, action| + deep_merge!(output_row, store_value(action.execute(row), field_path)) end - end + output_row + } end private def self.mapping_actions - @actions ||= [] + @actions ||= {} + end + + def self.store_value value, to + return {} if value.nil? + + to.reverse.inject({}) do |hash, key| + if hash.keys.first.nil? + hash.merge!(key => value) + else + { key => hash } + end + end end def self.deep_merge! hash, other_hash, &block diff --git a/lib/morfo/actions.rb b/lib/morfo/actions.rb index d7484e8..757d224 100644 --- a/lib/morfo/actions.rb +++ b/lib/morfo/actions.rb @@ -1,56 +1,91 @@ module Morfo module Actions - module ValueMethods - def extract_value from, row - Array(from).inject(row) do |resulting_value, key| - resulting_value ? resulting_value[key] : nil - end + class Field + attr_reader :field_path + attr_reader :actions + + def initialize field_path, actions + @field_path = field_path + @actions = actions end - def store_value to, value - return {} if value.nil? + def from *from_field_path + act = FromAction.new self, from_field_path + actions[field_path] = act + act + end - Array(to).reverse.inject({}) do |hash, key| - if hash.keys.first.nil? - hash.merge!(key => value) - else - { key => hash } - end - end + def calculated &calculate_blk + act = CalculationAction.new self, field_path, calculate_blk + actions[field_path] = act + act end - end - class MapAction - include ValueMethods - attr_reader :from - attr_reader :to + def execute row + raise ArgumentError, + "No field to get value from is specified for #{field_path.inspect}" + end + end - def initialize from, to - @from = from - @to = to + class CalculationAction + def initialize field, from_field_path, calculate_blk + @field = field + @from_field_path = from_field_path + @calculate_blk = calculate_blk end def execute row - store_value(to, extract_value(from, row)) + calculate_blk.call(row) end + + private + attr_reader :field + attr_reader :from_field_path + attr_reader :calculate_blk end - class TransformationAction - include ValueMethods - attr_reader :to - attr_reader :from - attr_reader :transformation + class FromAction + def initialize field, from_field_path + @field = field + @from_field_path = from_field_path + end - def initialize from, to, transformation - @from = from - @to = to - @transformation = transformation + def transformed &blk + act = TransformAction.new self, from_field_path, blk + field.actions[field.field_path] = act + act end def execute row - resulting_value = from ? extract_value(from, row) : nil - store_value(to, transformation.call(resulting_value,row)) + extract_value(from_field_path, row) end + + private + attr_reader :field + attr_reader :from_field_path + + def extract_value from, row + from.inject(row) do |resulting_value, key| + resulting_value ? resulting_value[key] : nil + end + end + end + + class TransformAction + def initialize previous_action, from_field_path, transform_blk + @previous_action = previous_action + @from_field_path = from_field_path + @transform_blk = transform_blk + end + + def execute row + transform_blk.call(previous_action.execute(row)) + end + + private + attr_reader :previous_action + attr_reader :from_field_path + attr_reader :transform_blk end end -end \ No newline at end of file +end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index a90497c..4ed5b88 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -34,19 +34,19 @@ context 'errors' do subject(:no_from) do class NilMorfer < Morfo::Base - field :my_field, {} + field(:my_field) end NilMorfer end it 'raises error for nil field' do - expect{no_from.morf([])}.to raise_error(ArgumentError) + expect{no_from.morf([{my_field: :something}])}.to raise_error(ArgumentError) end end context '1 to 1 conversion' do subject do class TitleMorfer < Morfo::Base - field :tv_show_title, from: :title + field(:tv_show_title).from(:title) end TitleMorfer end @@ -66,7 +66,7 @@ class TitleMorfer < Morfo::Base context '1 to 1 conversion with transformation' do subject do class NumCastMorfer < Morfo::Base - field(:cast_num, from: :cast){|v,r| v.size} + field(:cast_num).from(:cast).transformed{|v| v.size} end NumCastMorfer end @@ -80,8 +80,8 @@ class NumCastMorfer < Morfo::Base context '1 to many conversion' do subject do class MutliTitleMorfer < Morfo::Base - field :title, from: :title - field :also_title, from: :title + field(:title).from(:title) + field(:also_title).from(:title) end MutliTitleMorfer end @@ -96,21 +96,21 @@ class MutliTitleMorfer < Morfo::Base context 'nested source' do subject(:valid_path) do class ImdbRatingMorfer < Morfo::Base - field :rating, from: [:ratings, :imdb] + field(:rating).from(:ratings, :imdb) end ImdbRatingMorfer end subject(:valid_path_with_transformation) do - class ImdbRatingMorfer < Morfo::Base - field(:rating, from: [:ratings, :imdb]){|v| "Rating: #{v}"} + class ImdbRatingMorferWithTransformation < Morfo::Base + field(:rating).from(:ratings, :imdb).transformed {|v| "Rating: #{v}"} end - ImdbRatingMorfer + ImdbRatingMorferWithTransformation end subject(:invalid_path) do class InvalidImdbRatingMorfer < Morfo::Base - field :rating, from: [:very, :long, :path, :that, :might, :not, :exist] + field(:rating).from(:very, :long, :path, :that, :might, :not, :exist) end InvalidImdbRatingMorfer end @@ -134,8 +134,8 @@ class InvalidImdbRatingMorfer < Morfo::Base context 'nested destination' do subject do class WrapperMorfer < Morfo::Base - field([:tv_show, :title], from: :title) - field([:tv_show, :channel], from: :channel){|v| "Channel: #{v}"} + #field(:tv_show, :title).from(:title) + field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} end WrapperMorfer end @@ -144,7 +144,7 @@ class WrapperMorfer < Morfo::Base expected_output = input.map{|v| { tv_show: { - title: v[:title], + #title: v[:title], channel: "Channel: #{v[:channel]}", } } @@ -157,7 +157,7 @@ class WrapperMorfer < Morfo::Base context 'calculations' do subject do class TitlePrefixMorfer < Morfo::Base - field(:title_with_channel){|v,r| "#{r[:title]}, (#{r[:channel]})"} + field(:title_with_channel).calculated{|r| "#{r[:title]}, (#{r[:channel]})"} end TitlePrefixMorfer end @@ -175,7 +175,7 @@ class TitlePrefixMorfer < Morfo::Base context 'static values' do subject do class StaticTitleMorfer < Morfo::Base - field(:new_title){ 'Static Title' } + field(:new_title).calculated{ 'Static Title' } end StaticTitleMorfer end From fa52430a24736c6358eb777bf3815d7fbebb7a59 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 16:24:10 +0100 Subject: [PATCH 37/90] change README for new syntax --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6203126..e9b7088 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ Use the `field` method to specify what fields exist and where they will get thei ### Simple Mapping -The most basic form is, just define another field from the input hash. The value will just be copied. +The most basic form is copying the value from another field. class Title < Morfo::Base - field :tv_show_title, from: :title + field(:tv_show_title)from(:title) end Afterwards use the `morf` method to morf all hashes in one array to the end result: @@ -48,12 +48,12 @@ Afterwards use the `morf` method to morf all hashes in one array to the end resu # {tv_show_title: 'Breaking Bad'}, # ] -If you want to have access to nested values, you'll have to provide an array as the key: +If you want to have access to nested values, just provide the path to that field comma separated. class Name < Morfo::Base - field :first_name, from: [:name, :first] - field :last_name, from: [:name, :last] + field(:first_name).from(:name, :first) + field(:last_name).from(:name, :last) end Name.morf([ @@ -78,10 +78,10 @@ If you want to have access to nested values, you'll have to provide an array as ## Transformations -Every field can also take a transformation block, so that the original input can be transformed. +It's also possible to transform the value in any way ruby lets you transform a value. just provide a block in the `transformed` method. class AndZombies < Morfo::Base - field(:title, from: :title) {|title| "#{title} and Zombies"} + field(:title).from(title).transformed {|title| "#{title} and Zombies"} end AndZombies.morf([ @@ -94,11 +94,13 @@ Every field can also take a transformation block, so that the original input can # {title: 'Fifty Shades of Grey and Zombies'}, # ] -As the second argument, the whole row is passed into the block. So you can even do transformation based on the whole row. Or you can leave out all the arguments and return a static value. +## Calculations + +If the value of your field should be based on multiple fields of the input row, yoy can specify a calculation block via the `calculated` method. As an argument the whole input row is passed in. class NameConcatenator < Morfo::Base - field(:name) {|_, row| "#{row[:first_name]} #{row[:last_name]}"} - field(:status) { 'Best Friend' } + field(:name).calculated {|row| "#{row[:first_name]} #{row[:last_name]}"} + field(:status).calculated {'Best Friend'} end NameConcatenator.morf([ From fe5748b9e9342a0e238143714b055d2c0331b48b Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 13 Jan 2014 12:58:10 +0100 Subject: [PATCH 38/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index f63771c..ba765f3 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = '0.0.3' + VERSION = '0.1.0' end From 5adfed8d443a9cafd013f178b83ccfea9915f31a Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 17:03:08 +0100 Subject: [PATCH 39/90] make rbx work again --- Gemfile | 3 +++ morfo.gemspec | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Gemfile b/Gemfile index c6d58e3..b92655f 100644 --- a/Gemfile +++ b/Gemfile @@ -9,8 +9,11 @@ group :test, :development do gem 'guard-rspec' gem 'simplecov' gem 'pry' + gem 'rubinius-coverage', platform: :rbx gem 'rb-inotify', require: false gem 'rb-fsevent', require: false gem 'rb-fchange', require: false end + +gem 'json' diff --git a/morfo.gemspec b/morfo.gemspec index b470f16..1d6f8e4 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -19,6 +19,8 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'rake' + spec.add_dependency 'json' + spec.add_dependency 'rubysl' if RUBY_ENGINE == 'rbx' spec.add_development_dependency 'bundler', '~> 1.3' spec.add_development_dependency 'rspec', '>= 2.14', '< 4.0' From dec096e09813f78b9766f4c18f23760295d63d9e Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 17:03:31 +0100 Subject: [PATCH 40/90] update travis --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 401bc23..9ffba7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,15 @@ cache: bundler env: JRUBY_OPTS=--2.0 rvm: - 2.0.0 - - jruby-1.7.9 + - 2.1.0 + - jruby-1.7.10 - ruby-head - jruby-head - - rbx-2.2.1 + - rbx-2.2.3 matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head - - rvm: rbx-2.2.1 + - rvm: rbx-2.2.3 script: - bundle exec rake spec From 4120df3725fc529fc314640b37bb80d65b632344 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 17:33:54 +0100 Subject: [PATCH 41/90] add spec for nested destination --- spec/lib/morfo_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 4ed5b88..cf1f9c2 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -134,7 +134,7 @@ class InvalidImdbRatingMorfer < Morfo::Base context 'nested destination' do subject do class WrapperMorfer < Morfo::Base - #field(:tv_show, :title).from(:title) + field(:tv_show, :title).from(:title) field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} end WrapperMorfer @@ -144,7 +144,7 @@ class WrapperMorfer < Morfo::Base expected_output = input.map{|v| { tv_show: { - #title: v[:title], + title: v[:title], channel: "Channel: #{v[:channel]}", } } From 91fae096b94e3e6dfb12ee7d98172ddb49d7c7a6 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 16 Jan 2014 10:11:35 +0100 Subject: [PATCH 42/90] i update version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index ba765f3..4da6385 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = '0.1.0' + VERSION = '0.2.0' end From 51e9e773730e2096c2e7e29996ceea304a0d85e8 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 15 Jan 2014 17:16:23 +0100 Subject: [PATCH 43/90] separate contributing guideline --- CONTRIBUTING.md | 7 +++++++ README.md | 9 --------- 2 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0c9a6f3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# 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 diff --git a/README.md b/README.md index e9b7088..2a7afd3 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,3 @@ If the value of your field should be based on multiple fields of the input row, # {:name=>"Robin Hood", :status=>"Best Friend"}, # {:name=>"Sherlock Holmes", :status=>'Best Friend'} # ] - - -## 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 From fe36f75f8ffe3e39e68241ba8ff143dadce279c2 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 17 Jan 2014 13:28:55 +0100 Subject: [PATCH 44/90] fix syntax --- benchmarks/data.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmarks/data.rb b/benchmarks/data.rb index d55217e..d5308c5 100644 --- a/benchmarks/data.rb +++ b/benchmarks/data.rb @@ -46,20 +46,20 @@ def stringify_keys hash class SimpleMorferSymbol < Morfo::Base BenchmarkData.row.keys.each do |field| - field(:"#{field}_mapped", from: field) + field(:"#{field}_mapped").from(field) end end class SimpleMorferString < Morfo::Base BenchmarkData.row_string_keys.keys.each do |field| - field("#{field}_mapped", from: field) + field("#{field}_mapped").from(field) end end class NestedMorferSymbol < Morfo::Base BenchmarkData.row_nested.each do |key, value| value.keys.each do |field| - field(:"#{field}_mapped", from: [key, field]) + field(:"#{field}_mapped").from(key, field) end end end @@ -67,7 +67,7 @@ class NestedMorferSymbol < Morfo::Base class NestedMorferString < Morfo::Base BenchmarkData.row_nested_string_keys.each do |key, value| value.keys.each do |field| - field("#{field}_mapped", from: [key, field]) + field("#{field}_mapped").from(key, field) end end end From 0890ab892d88d877a0e5fff88ea526396586ff26 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 17 Jan 2014 15:27:58 +0100 Subject: [PATCH 45/90] improve output --- benchmarks/run.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmarks/run.rb b/benchmarks/run.rb index e0ea8e9..ffa56f1 100644 --- a/benchmarks/run.rb +++ b/benchmarks/run.rb @@ -34,6 +34,7 @@ ) end +puts "running on: #{RUBY_ENGINE}:" Benchmark.bm(20) do |x| definitions.each do |defintition| x.report(defintition[:label]) do @@ -43,4 +44,5 @@ end end end +puts "====" From 460fb0676dd05026f22e3172863f4bcb68477fc0 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 24 Oct 2014 19:03:40 +0200 Subject: [PATCH 46/90] adjust syntax of Guardfile --- Guardfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guardfile b/Guardfile index 85d551a..c96cbe6 100644 --- a/Guardfile +++ b/Guardfile @@ -1,7 +1,7 @@ # A sample Guardfile # More info at https://github.com/guard/guard#readme -guard :rspec, all_on_start: true do +guard :rspec, cmd: 'bundle exec rspec', all_on_start: true do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { 'spec' } From ffad68eaecfabef427103864820cebc48797e920 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 24 Oct 2014 19:04:04 +0200 Subject: [PATCH 47/90] add possibility to morf a single line --- lib/morfo.rb | 16 +-- spec/lib/morfo_spec.rb | 222 +++++++++++++++++++++++++++++------------ 2 files changed, 169 insertions(+), 69 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 168d01a..a78905d 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -10,13 +10,15 @@ def self.field *field_path end def self.morf input - input.map {|row| - output_row = {} - mapping_actions.each do |field_path, action| - deep_merge!(output_row, store_value(action.execute(row), field_path)) - end - output_row - } + input.map { |row| morf_single(row) } + end + + def self.morf_single input + output = {} + mapping_actions.each do |field_path, action| + deep_merge!(output, store_value(action.execute(input), field_path)) + end + output end private diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index cf1f9c2..f8bcc87 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -30,27 +30,40 @@ ] end - describe '#morf' do - context 'errors' do - subject(:no_from) do - class NilMorfer < Morfo::Base - field(:my_field) - end - NilMorfer + let(:single_input) do + input.first + end + + context 'errors' do + subject(:no_from) do + class NilMorfer < Morfo::Base + field(:my_field) end + NilMorfer + end + + describe '#morf' do it 'raises error for nil field' do expect{no_from.morf([{my_field: :something}])}.to raise_error(ArgumentError) end end - context '1 to 1 conversion' do - subject do - class TitleMorfer < Morfo::Base - field(:tv_show_title).from(:title) - end - TitleMorfer + describe '#morf_single' do + it 'raises error for nil field' do + expect{no_from.morf_single({my_field: :something})}.to raise_error(ArgumentError) + end + end + end + + context '1 to 1 conversion' do + subject do + class TitleMorfer < Morfo::Base + field(:tv_show_title).from(:title) end + TitleMorfer + end + describe '#morf' do it 'maps title correctly' do expected_output = input.map{|v| {tv_show_title: v[:title]} } expect(subject.morf(input)).to eq(expected_output) @@ -63,58 +76,91 @@ class TitleMorfer < Morfo::Base end end - context '1 to 1 conversion with transformation' do - subject do - class NumCastMorfer < Morfo::Base - field(:cast_num).from(:cast).transformed{|v| v.size} - end - NumCastMorfer + describe '#morf_single' do + it 'maps title correctly' do + expected_output = { tv_show_title: single_input[:title] } + expect(subject.morf_single(single_input)).to eq(expected_output) end + it 'leaves out nil values in result' do + expected_output = {} + modified_input = single_input.reject { |k, v| k == :title } + expect(subject.morf_single(modified_input)).to eq(expected_output) + end + end + end + + context '1 to 1 conversion with transformation' do + subject do + class NumCastMorfer < Morfo::Base + field(:cast_num).from(:cast).transformed{|v| v.size} + end + NumCastMorfer + end + + describe '#morf' do it 'calls transformation correctly' do expected_output = input.map{|v| {cast_num: v[:cast].size} } expect(subject.morf(input)).to eq(expected_output) end end - context '1 to many conversion' do - subject do - class MutliTitleMorfer < Morfo::Base - field(:title).from(:title) - field(:also_title).from(:title) - end - MutliTitleMorfer + describe '#morf_single' do + it 'calls transformation correctly' do + expected_output = { cast_num: single_input[:cast].size } + expect(subject.morf_single(single_input)).to eq(expected_output) end + end + end + context '1 to many conversion' do + subject do + class MutliTitleMorfer < Morfo::Base + field(:title).from(:title) + field(:also_title).from(:title) + end + MutliTitleMorfer + end + + describe '#morf' do it 'maps title to multiple fields' do expected_output = input.map{|v| {title: v[:title], also_title: v[:title]} } expect(subject.morf(input)).to eq(expected_output) end end - context 'nested conversion' do - context 'nested source' do - subject(:valid_path) do - class ImdbRatingMorfer < Morfo::Base - field(:rating).from(:ratings, :imdb) - end - ImdbRatingMorfer + describe '#morf_single' do + it 'maps title to multiple fields' do + expected_output = {title: single_input[:title], also_title: single_input[:title]} + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end + end + + context 'nested conversion' do + context 'nested source' do + subject(:valid_path) do + class ImdbRatingMorfer < Morfo::Base + field(:rating).from(:ratings, :imdb) end + ImdbRatingMorfer + end - subject(:valid_path_with_transformation) do - class ImdbRatingMorferWithTransformation < Morfo::Base - field(:rating).from(:ratings, :imdb).transformed {|v| "Rating: #{v}"} - end - ImdbRatingMorferWithTransformation + subject(:valid_path_with_transformation) do + class ImdbRatingMorferWithTransformation < Morfo::Base + field(:rating).from(:ratings, :imdb).transformed {|v| "Rating: #{v}"} end + ImdbRatingMorferWithTransformation + end - subject(:invalid_path) do - class InvalidImdbRatingMorfer < Morfo::Base - field(:rating).from(:very, :long, :path, :that, :might, :not, :exist) - end - InvalidImdbRatingMorfer + subject(:invalid_path) do + class InvalidImdbRatingMorfer < Morfo::Base + field(:rating).from(:very, :long, :path, :that, :might, :not, :exist) end + InvalidImdbRatingMorfer + end + describe '#morf' do it 'maps nested attributes' do expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } expect(valid_path.morf(input)).to eq(expected_output) @@ -131,15 +177,34 @@ class InvalidImdbRatingMorfer < Morfo::Base end end - context 'nested destination' do - subject do - class WrapperMorfer < Morfo::Base - field(:tv_show, :title).from(:title) - field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} - end - WrapperMorfer + describe '#morf_single' do + it 'maps nested attributes' do + expected_output = {rating: single_input[:ratings][:imdb]} + expect(valid_path.morf_single(single_input)).to eq(expected_output) + end + + it 'maps nested attributes with transformation' do + expected_output = {rating: "Rating: #{single_input[:ratings][:imdb]}"} + expect(valid_path_with_transformation.morf_single(single_input)).to eq(expected_output) + end + + it 'doesn\'t raise error for invalid path' do + expected_output = { } + expect(invalid_path.morf_single(single_input)).to eq(expected_output) end + end + end + context 'nested destination' do + subject do + class WrapperMorfer < Morfo::Base + field(:tv_show, :title).from(:title) + field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} + end + WrapperMorfer + end + + describe '#morf' do it 'maps to nested destination' do expected_output = input.map{|v| { @@ -152,16 +217,30 @@ class WrapperMorfer < Morfo::Base expect(subject.morf(input)).to eq(expected_output) end end - end - context 'calculations' do - subject do - class TitlePrefixMorfer < Morfo::Base - field(:title_with_channel).calculated{|r| "#{r[:title]}, (#{r[:channel]})"} + describe '#morf_single' do + it 'maps to nested destination' do + expected_output = { + tv_show: { + title: single_input[:title], + channel: "Channel: #{single_input[:channel]}", + } + } + expect(subject.morf_single(single_input)).to eq(expected_output) end - TitlePrefixMorfer end + end + end + + context 'calculations' do + subject do + class TitlePrefixMorfer < Morfo::Base + field(:title_with_channel).calculated{|r| "#{r[:title]}, (#{r[:channel]})"} + end + TitlePrefixMorfer + end + describe '#morf' do it 'maps calculation correctly' do expected_output = input.map{|r| { @@ -172,18 +251,37 @@ class TitlePrefixMorfer < Morfo::Base end end - context 'static values' do - subject do - class StaticTitleMorfer < Morfo::Base - field(:new_title).calculated{ 'Static Title' } - end - StaticTitleMorfer + describe '#morf_single' do + it 'maps calculation correctly' do + expected_output = { + title_with_channel: "#{single_input[:title]}, (#{single_input[:channel]})" + } + + expect(subject.morf_single(single_input)).to eq(expected_output) end + end + end + + context 'static values' do + subject do + class StaticTitleMorfer < Morfo::Base + field(:new_title).calculated{ 'Static Title' } + end + StaticTitleMorfer + end + describe '#morf' do it 'maps static value correctly' do expected_output = input.map{|r| {new_title: 'Static Title'} } expect(subject.morf(input)).to eq(expected_output) end end + + describe '#morf_single' do + it 'maps static value correctly' do + expected_output = { new_title: 'Static Title' } + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end end end From c259fb4dddd78bc8b72e5c4b3f149c669c05710d Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 24 Oct 2014 19:13:13 +0200 Subject: [PATCH 48/90] update travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9ffba7b..492b26b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,13 @@ cache: bundler env: JRUBY_OPTS=--2.0 rvm: - 2.0.0 - - 2.1.0 - - jruby-1.7.10 + - 2.1.3 + - jruby-1.7.16 - ruby-head - jruby-head - rbx-2.2.3 matrix: + fast_finish: true allow_failures: - rvm: ruby-head - rvm: jruby-head From 8d1daa88d5c79748c911688404bb1f1045ed18e0 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Tue, 28 Oct 2014 10:52:46 +0100 Subject: [PATCH 49/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 4da6385..644d127 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = '0.2.0' + VERSION = '0.3.0' end From 85a8dc0f1da099e68b784c8d1f2ecc69b16cf62e Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 22 Apr 2015 13:00:45 -0700 Subject: [PATCH 50/90] coding style --- CONTRIBUTING.md | 2 +- Gemfile | 22 ++++---- Guardfile | 4 +- README.md | 38 ++++++------- Rakefile | 4 +- benchmarks/data.rb | 20 +++---- benchmarks/run.rb | 14 ++--- lib/morfo.rb | 5 +- lib/morfo/version.rb | 2 +- morfo.gemspec | 26 ++++----- spec/lib/morfo_spec.rb | 118 ++++++++++++++++++++--------------------- spec/spec_helper.rb | 10 ++-- 12 files changed, 133 insertions(+), 132 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c9a6f3..cd629dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,6 @@ 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some 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 diff --git a/Gemfile b/Gemfile index b92655f..c07214c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,19 +1,19 @@ -source 'https://rubygems.org' +source "https://rubygems.org" # Specify your gem's dependencies in morfo.gemspec gemspec group :test, :development do - gem 'coveralls', require: false - gem 'guard' - gem 'guard-rspec' - gem 'simplecov' - gem 'pry' - gem 'rubinius-coverage', platform: :rbx + gem "coveralls", require: false + gem "guard" + gem "guard-rspec" + gem "simplecov" + gem "pry" + gem "rubinius-coverage", platform: :rbx - gem 'rb-inotify', require: false - gem 'rb-fsevent', require: false - gem 'rb-fchange', require: false + gem "rb-inotify", require: false + gem "rb-fsevent", require: false + gem "rb-fchange", require: false end -gem 'json' +gem "json" diff --git a/Guardfile b/Guardfile index c96cbe6..76e7247 100644 --- a/Guardfile +++ b/Guardfile @@ -1,8 +1,8 @@ # A sample Guardfile # More info at https://github.com/guard/guard#readme -guard :rspec, cmd: 'bundle exec rspec', all_on_start: true do +guard :rspec, cmd: "bundle exec rspec", all_on_start: true do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { 'spec' } + watch("spec/spec_helper.rb") { "spec" } end diff --git a/README.md b/README.md index 2a7afd3..52d1119 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This gem is currently only tested on Ruby 2.0 (including 2.0 mode of JRuby and R Add this line to your application's Gemfile: - gem 'morfo' + gem "morfo" And then execute: @@ -39,13 +39,13 @@ The most basic form is copying the value from another field. Afterwards use the `morf` method to morf all hashes in one array to the end result: Title.morf([ - {title: 'The Walking Dead'} , - {title: 'Breaking Bad'}, + {title: "The Walking Dead"} , + {title: "Breaking Bad"}, ]) # [ - # {tv_show_title: 'The Walking Dead'}, - # {tv_show_title: 'Breaking Bad'}, + # {tv_show_title: "The Walking Dead"}, + # {tv_show_title: "Breaking Bad"}, # ] If you want to have access to nested values, just provide the path to that field comma separated. @@ -59,21 +59,21 @@ If you want to have access to nested values, just provide the path to that field Name.morf([ { name: { - first: 'Clark', - last: 'Kent', + first: "Clark", + last: "Kent", }, }, { name: { - first: 'Bruce', - last: 'Wayne', + first: "Bruce", + last: "Wayne", }, }, ]) # [ - # {first_name: 'Clark',last_name: 'Kent'}, - # {first_name: 'Bruce',last_name: 'Wayne'}, + # {first_name: "Clark", last_name: "Kent"}, + # {first_name: "Bruce", last_name: "Wayne"}, # ] ## Transformations @@ -85,13 +85,13 @@ It's also possible to transform the value in any way ruby lets you transform a v end AndZombies.morf([ - {title: 'Pride and Prejudice'}, - {title: 'Fifty Shades of Grey'}, + {title: "Pride and Prejudice"}, + {title: "Fifty Shades of Grey"}, ]) # [ - # {title: 'Pride and Prejudice and Zombies'}, - # {title: 'Fifty Shades of Grey and Zombies'}, + # {title: "Pride and Prejudice and Zombies"}, + # {title: "Fifty Shades of Grey and Zombies"}, # ] ## Calculations @@ -100,15 +100,15 @@ If the value of your field should be based on multiple fields of the input row, class NameConcatenator < Morfo::Base field(:name).calculated {|row| "#{row[:first_name]} #{row[:last_name]}"} - field(:status).calculated {'Best Friend'} + field(:status).calculated {"Best Friend"} end NameConcatenator.morf([ - {first_name: 'Robin', last_name: 'Hood'}, - {first_name: 'Sherlock', last_name: 'Holmes'}, + {first_name: "Robin", last_name: "Hood"}, + {first_name: "Sherlock", last_name: "Holmes"}, ]) # [ # {:name=>"Robin Hood", :status=>"Best Friend"}, - # {:name=>"Sherlock Holmes", :status=>'Best Friend'} + # {:name=>"Sherlock Holmes", :status=>"Best Friend"} # ] diff --git a/Rakefile b/Rakefile index a81dfd8..7a720b9 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,4 @@ -require 'bundler/gem_tasks' -require 'rspec/core/rake_task' +require "bundler/gem_tasks" +require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) diff --git a/benchmarks/data.rb b/benchmarks/data.rb index d5308c5..463b2f5 100644 --- a/benchmarks/data.rb +++ b/benchmarks/data.rb @@ -7,16 +7,16 @@ def nested_wrapper def row { - first_name: 'Jazmyn', - last_name: 'Willms', - gender: 'female', - phone_number: '485-675-9228', - cell_phone: '1-172-435-9402 x4907', - street_name: 'Becker Inlet', - street_number: '15a', - city: 'Carolynchester', - zip: '38189', - country: 'USA', + first_name: "Jazmyn", + last_name: "Willms", + gender: "female", + phone_number: "485-675-9228", + cell_phone: "1-172-435-9402 x4907", + street_name: "Becker Inlet", + street_number: "15a", + city: "Carolynchester", + zip: "38189", + country: "USA", } end diff --git a/benchmarks/run.rb b/benchmarks/run.rb index ffa56f1..01bfe92 100644 --- a/benchmarks/run.rb +++ b/benchmarks/run.rb @@ -1,28 +1,28 @@ -require 'morfo' -require 'benchmark' -require './benchmarks/data' +require "morfo" +require "benchmark" +require "./benchmarks/data" iterations = 100 batch_size = 10000 definitions = [ { - label: 'Simple (strings)', + label: "Simple (strings)", row: BenchmarkData.row_string_keys, morf_class: BenchmarkData::SimpleMorferString }, { - label: 'Simple (symbols)', + label: "Simple (symbols)", row: BenchmarkData.row, morf_class: BenchmarkData::SimpleMorferSymbol }, { - label: 'Nested (strings)', + label: "Nested (strings)", row: BenchmarkData.row_nested_string_keys, morf_class: BenchmarkData::NestedMorferString }, { - label: 'Nested (symbols)', + label: "Nested (symbols)", row: BenchmarkData.row_nested, morf_class: BenchmarkData::NestedMorferSymbol }, diff --git a/lib/morfo.rb b/lib/morfo.rb index a78905d..b0e5f26 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,5 +1,6 @@ -require 'morfo/version' -require 'morfo/actions' +require "morfo/version" +require "morfo/actions" +require "morfo/deserializer" module Morfo class Base diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 644d127..c835bed 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = '0.3.0' + VERSION = "0.3.0" end diff --git a/morfo.gemspec b/morfo.gemspec index 1d6f8e4..7d20039 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -1,27 +1,27 @@ # coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'morfo/version' +require "morfo/version" Gem::Specification.new do |spec| - spec.name = 'morfo' + spec.name = "morfo" spec.version = Morfo::VERSION - spec.authors = ['Leif Gensert'] - spec.email = ['leifg@gmx.de'] + spec.authors = ["Leif Gensert"] + spec.email = ["leifg@gmx.de"] spec.description = %q{This gem provides a DSL for converting one hash into another} spec.summary = %q{Inspired by ActiveImporter, this gem generically converts an array of hashes} - spec.homepage = '' - spec.license = 'MIT' + spec.homepage = "" + spec.license = "MIT" spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ['lib'] + spec.require_paths = ["lib"] - spec.add_dependency 'rake' - spec.add_dependency 'json' - spec.add_dependency 'rubysl' if RUBY_ENGINE == 'rbx' + spec.add_dependency "rake" + spec.add_dependency "json" + spec.add_dependency "rubysl" if RUBY_ENGINE == "rbx" - spec.add_development_dependency 'bundler', '~> 1.3' - spec.add_development_dependency 'rspec', '>= 2.14', '< 4.0' + spec.add_development_dependency "bundler", "~> 1.3" + spec.add_development_dependency "rspec", ">= 2.14", "< 4.0" end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index f8bcc87..f9bb8f6 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -1,14 +1,14 @@ -require 'spec_helper' +require "spec_helper" describe Morfo::Base do let(:input) do [ { - title: 'The Walking Dead', - channel: 'AMC', + title: "The Walking Dead", + channel: "AMC", watchers: 1337, - status: 'running', - cast: ['Lincoln, Andrew', 'McBride, Melissa'], + status: "running", + cast: ["Lincoln, Andrew", "McBride, Melissa"], ratings: { imdb: 8.7, trakt: 89, @@ -16,11 +16,11 @@ }, }, { - title: 'Breaking Bad', - channel: 'AMC', + title: "Breaking Bad", + channel: "AMC", watchers: 72891, - status: 'ended', - cast: ['Cranston, Bryan', 'Gunn, Anna'], + status: "ended", + cast: ["Cranston, Bryan", "Gunn, Anna"], ratings: { imdb: 9.5, trakt: 95, @@ -34,7 +34,7 @@ input.first end - context 'errors' do + context "errors" do subject(:no_from) do class NilMorfer < Morfo::Base field(:my_field) @@ -42,20 +42,20 @@ class NilMorfer < Morfo::Base NilMorfer end - describe '#morf' do - it 'raises error for nil field' do + describe "#morf" do + it "raises error for nil field" do expect{no_from.morf([{my_field: :something}])}.to raise_error(ArgumentError) end end - describe '#morf_single' do - it 'raises error for nil field' do + describe "#morf_single" do + it "raises error for nil field" do expect{no_from.morf_single({my_field: :something})}.to raise_error(ArgumentError) end end end - context '1 to 1 conversion' do + context "1 to 1 conversion" do subject do class TitleMorfer < Morfo::Base field(:tv_show_title).from(:title) @@ -63,26 +63,26 @@ class TitleMorfer < Morfo::Base TitleMorfer end - describe '#morf' do - it 'maps title correctly' do + describe "#morf" do + it "maps title correctly" do expected_output = input.map{|v| {tv_show_title: v[:title]} } expect(subject.morf(input)).to eq(expected_output) end - it 'leaves out nil values in result' do + it "leaves out nil values in result" do expected_output = [{},{}] modified_input = input.map{|h| h.reject{|k, v| k == :title}} expect(subject.morf(modified_input)).to eq(expected_output) end end - describe '#morf_single' do - it 'maps title correctly' do + describe "#morf_single" do + it "maps title correctly" do expected_output = { tv_show_title: single_input[:title] } expect(subject.morf_single(single_input)).to eq(expected_output) end - it 'leaves out nil values in result' do + it "leaves out nil values in result" do expected_output = {} modified_input = single_input.reject { |k, v| k == :title } expect(subject.morf_single(modified_input)).to eq(expected_output) @@ -90,7 +90,7 @@ class TitleMorfer < Morfo::Base end end - context '1 to 1 conversion with transformation' do + context "1 to 1 conversion with transformation" do subject do class NumCastMorfer < Morfo::Base field(:cast_num).from(:cast).transformed{|v| v.size} @@ -98,22 +98,22 @@ class NumCastMorfer < Morfo::Base NumCastMorfer end - describe '#morf' do - it 'calls transformation correctly' do + describe "#morf" do + it "calls transformation correctly" do expected_output = input.map{|v| {cast_num: v[:cast].size} } expect(subject.morf(input)).to eq(expected_output) end end - describe '#morf_single' do - it 'calls transformation correctly' do + describe "#morf_single" do + it "calls transformation correctly" do expected_output = { cast_num: single_input[:cast].size } expect(subject.morf_single(single_input)).to eq(expected_output) end end end - context '1 to many conversion' do + context "1 to many conversion" do subject do class MutliTitleMorfer < Morfo::Base field(:title).from(:title) @@ -122,23 +122,23 @@ class MutliTitleMorfer < Morfo::Base MutliTitleMorfer end - describe '#morf' do - it 'maps title to multiple fields' do + describe "#morf" do + it "maps title to multiple fields" do expected_output = input.map{|v| {title: v[:title], also_title: v[:title]} } expect(subject.morf(input)).to eq(expected_output) end end - describe '#morf_single' do - it 'maps title to multiple fields' do + describe "#morf_single" do + it "maps title to multiple fields" do expected_output = {title: single_input[:title], also_title: single_input[:title]} expect(subject.morf_single(single_input)).to eq(expected_output) end end end - context 'nested conversion' do - context 'nested source' do + context "nested conversion" do + context "nested source" do subject(:valid_path) do class ImdbRatingMorfer < Morfo::Base field(:rating).from(:ratings, :imdb) @@ -160,42 +160,42 @@ class InvalidImdbRatingMorfer < Morfo::Base InvalidImdbRatingMorfer end - describe '#morf' do - it 'maps nested attributes' do + describe "#morf" do + it "maps nested attributes" do expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } expect(valid_path.morf(input)).to eq(expected_output) end - it 'maps nested attributes with transformation' do + it "maps nested attributes with transformation" do expected_output = input.map{|v| {rating: "Rating: #{v[:ratings][:imdb]}"} } expect(valid_path_with_transformation.morf(input)).to eq(expected_output) end - it 'doesn\'t raise error for invalid path' do + it "doesn't raise error for invalid path" do expected_output = [{},{}] expect(invalid_path.morf(input)).to eq(expected_output) end end - describe '#morf_single' do - it 'maps nested attributes' do + describe "#morf_single" do + it "maps nested attributes" do expected_output = {rating: single_input[:ratings][:imdb]} expect(valid_path.morf_single(single_input)).to eq(expected_output) end - it 'maps nested attributes with transformation' do + it "maps nested attributes with transformation" do expected_output = {rating: "Rating: #{single_input[:ratings][:imdb]}"} expect(valid_path_with_transformation.morf_single(single_input)).to eq(expected_output) end - it 'doesn\'t raise error for invalid path' do + it "doesn't raise error for invalid path" do expected_output = { } expect(invalid_path.morf_single(single_input)).to eq(expected_output) end end end - context 'nested destination' do + context "nested destination" do subject do class WrapperMorfer < Morfo::Base field(:tv_show, :title).from(:title) @@ -204,8 +204,8 @@ class WrapperMorfer < Morfo::Base WrapperMorfer end - describe '#morf' do - it 'maps to nested destination' do + describe "#morf" do + it "maps to nested destination" do expected_output = input.map{|v| { tv_show: { @@ -218,8 +218,8 @@ class WrapperMorfer < Morfo::Base end end - describe '#morf_single' do - it 'maps to nested destination' do + describe "#morf_single" do + it "maps to nested destination" do expected_output = { tv_show: { title: single_input[:title], @@ -232,7 +232,7 @@ class WrapperMorfer < Morfo::Base end end - context 'calculations' do + context "calculations" do subject do class TitlePrefixMorfer < Morfo::Base field(:title_with_channel).calculated{|r| "#{r[:title]}, (#{r[:channel]})"} @@ -240,8 +240,8 @@ class TitlePrefixMorfer < Morfo::Base TitlePrefixMorfer end - describe '#morf' do - it 'maps calculation correctly' do + describe "#morf" do + it "maps calculation correctly" do expected_output = input.map{|r| { title_with_channel: "#{r[:title]}, (#{r[:channel]})" @@ -251,8 +251,8 @@ class TitlePrefixMorfer < Morfo::Base end end - describe '#morf_single' do - it 'maps calculation correctly' do + describe "#morf_single" do + it "maps calculation correctly" do expected_output = { title_with_channel: "#{single_input[:title]}, (#{single_input[:channel]})" } @@ -262,24 +262,24 @@ class TitlePrefixMorfer < Morfo::Base end end - context 'static values' do + context "static values" do subject do class StaticTitleMorfer < Morfo::Base - field(:new_title).calculated{ 'Static Title' } + field(:new_title).calculated{ "Static Title" } end StaticTitleMorfer end - describe '#morf' do - it 'maps static value correctly' do - expected_output = input.map{|r| {new_title: 'Static Title'} } + describe "#morf" do + it "maps static value correctly" do + expected_output = input.map{|r| {new_title: "Static Title"} } expect(subject.morf(input)).to eq(expected_output) end end - describe '#morf_single' do - it 'maps static value correctly' do - expected_output = { new_title: 'Static Title' } + describe "#morf_single" do + it "maps static value correctly" do + expected_output = { new_title: "Static Title" } expect(subject.morf_single(single_input)).to eq(expected_output) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 39c452f..11c6f9b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,10 @@ -require 'simplecov' -require 'coveralls' +require "simplecov" +require "coveralls" SimpleCov.formatter = Coveralls::SimpleCov::Formatter SimpleCov.start do - add_filter 'spec' + add_filter "spec" end -require 'rspec' -require 'morfo' +require "rspec" +require "morfo" From 4b3a90c65b8d8f52a341e0e2659ad5e58f54fde1 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 22 Apr 2015 16:08:33 -0700 Subject: [PATCH 51/90] centralize checking of correct output --- spec/lib/morfo_spec.rb | 270 +++++--------------------------- spec/spec_helper.rb | 2 + spec/support/shared_context.rb | 34 ++++ spec/support/shared_examples.rb | 227 +++++++++++++++++++++++++++ 4 files changed, 300 insertions(+), 233 deletions(-) create mode 100644 spec/support/shared_context.rb create mode 100644 spec/support/shared_examples.rb diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index f9bb8f6..618a9e6 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -1,287 +1,91 @@ require "spec_helper" describe Morfo::Base do - let(:input) do - [ - { - title: "The Walking Dead", - channel: "AMC", - watchers: 1337, - status: "running", - cast: ["Lincoln, Andrew", "McBride, Melissa"], - ratings: { - imdb: 8.7, - trakt: 89, - rotten_tomatoes: 93, - }, - }, - { - title: "Breaking Bad", - channel: "AMC", - watchers: 72891, - status: "ended", - cast: ["Cranston, Bryan", "Gunn, Anna"], - ratings: { - imdb: 9.5, - trakt: 95, - rotten_tomatoes: 100, - }, - } - ] - end - - let(:single_input) do - input.first - end - - context "errors" do + it_behaves_like "an error throwing morfer" do subject(:no_from) do class NilMorfer < Morfo::Base field(:my_field) end NilMorfer end - - describe "#morf" do - it "raises error for nil field" do - expect{no_from.morf([{my_field: :something}])}.to raise_error(ArgumentError) - end - end - - describe "#morf_single" do - it "raises error for nil field" do - expect{no_from.morf_single({my_field: :something})}.to raise_error(ArgumentError) - end - end end - context "1 to 1 conversion" do + it_behaves_like "a 1 to 1 morfer" do subject do class TitleMorfer < Morfo::Base field(:tv_show_title).from(:title) end TitleMorfer end - - describe "#morf" do - it "maps title correctly" do - expected_output = input.map{|v| {tv_show_title: v[:title]} } - expect(subject.morf(input)).to eq(expected_output) - end - - it "leaves out nil values in result" do - expected_output = [{},{}] - modified_input = input.map{|h| h.reject{|k, v| k == :title}} - expect(subject.morf(modified_input)).to eq(expected_output) - end - end - - describe "#morf_single" do - it "maps title correctly" do - expected_output = { tv_show_title: single_input[:title] } - expect(subject.morf_single(single_input)).to eq(expected_output) - end - - it "leaves out nil values in result" do - expected_output = {} - modified_input = single_input.reject { |k, v| k == :title } - expect(subject.morf_single(modified_input)).to eq(expected_output) - end - end end - context "1 to 1 conversion with transformation" do + it_behaves_like "a 1 to 1 morfer with transformation" do subject do - class NumCastMorfer < Morfo::Base - field(:cast_num).from(:cast).transformed{|v| v.size} - end - NumCastMorfer - end - - describe "#morf" do - it "calls transformation correctly" do - expected_output = input.map{|v| {cast_num: v[:cast].size} } - expect(subject.morf(input)).to eq(expected_output) - end - end - - describe "#morf_single" do - it "calls transformation correctly" do - expected_output = { cast_num: single_input[:cast].size } - expect(subject.morf_single(single_input)).to eq(expected_output) + class AndZombies < Morfo::Base + field(:title).from(:title).transformed{|v| "#{v} and Zombies"} end + AndZombies end end - context "1 to many conversion" do + it_behaves_like "a 1 to many morfer" do subject do - class MutliTitleMorfer < Morfo::Base + class MultiTitleMorfer < Morfo::Base field(:title).from(:title) field(:also_title).from(:title) end - MutliTitleMorfer - end - - describe "#morf" do - it "maps title to multiple fields" do - expected_output = input.map{|v| {title: v[:title], also_title: v[:title]} } - expect(subject.morf(input)).to eq(expected_output) - end - end - - describe "#morf_single" do - it "maps title to multiple fields" do - expected_output = {title: single_input[:title], also_title: single_input[:title]} - expect(subject.morf_single(single_input)).to eq(expected_output) - end + MultiTitleMorfer end end - context "nested conversion" do - context "nested source" do - subject(:valid_path) do - class ImdbRatingMorfer < Morfo::Base - field(:rating).from(:ratings, :imdb) - end - ImdbRatingMorfer - end - - subject(:valid_path_with_transformation) do - class ImdbRatingMorferWithTransformation < Morfo::Base - field(:rating).from(:ratings, :imdb).transformed {|v| "Rating: #{v}"} - end - ImdbRatingMorferWithTransformation - end - - subject(:invalid_path) do - class InvalidImdbRatingMorfer < Morfo::Base - field(:rating).from(:very, :long, :path, :that, :might, :not, :exist) - end - InvalidImdbRatingMorfer - end - - describe "#morf" do - it "maps nested attributes" do - expected_output = input.map{|v| {rating: v[:ratings][:imdb]} } - expect(valid_path.morf(input)).to eq(expected_output) - end - - it "maps nested attributes with transformation" do - expected_output = input.map{|v| {rating: "Rating: #{v[:ratings][:imdb]}"} } - expect(valid_path_with_transformation.morf(input)).to eq(expected_output) - end - - it "doesn't raise error for invalid path" do - expected_output = [{},{}] - expect(invalid_path.morf(input)).to eq(expected_output) - end - end - - describe "#morf_single" do - it "maps nested attributes" do - expected_output = {rating: single_input[:ratings][:imdb]} - expect(valid_path.morf_single(single_input)).to eq(expected_output) - end - - it "maps nested attributes with transformation" do - expected_output = {rating: "Rating: #{single_input[:ratings][:imdb]}"} - expect(valid_path_with_transformation.morf_single(single_input)).to eq(expected_output) - end - - it "doesn't raise error for invalid path" do - expected_output = { } - expect(invalid_path.morf_single(single_input)).to eq(expected_output) - end - end - end - - context "nested destination" do - subject do - class WrapperMorfer < Morfo::Base - field(:tv_show, :title).from(:title) - field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} - end - WrapperMorfer - end - - describe "#morf" do - it "maps to nested destination" do - expected_output = input.map{|v| - { - tv_show: { - title: v[:title], - channel: "Channel: #{v[:channel]}", - } - } - } - expect(subject.morf(input)).to eq(expected_output) - end - end - - describe "#morf_single" do - it "maps to nested destination" do - expected_output = { - tv_show: { - title: single_input[:title], - channel: "Channel: #{single_input[:channel]}", - } - } - expect(subject.morf_single(single_input)).to eq(expected_output) - end - end - end - end - - context "calculations" do + it_behaves_like "a calculating morfer" do subject do class TitlePrefixMorfer < Morfo::Base field(:title_with_channel).calculated{|r| "#{r[:title]}, (#{r[:channel]})"} end TitlePrefixMorfer end + end - describe "#morf" do - it "maps calculation correctly" do - expected_output = input.map{|r| - { - title_with_channel: "#{r[:title]}, (#{r[:channel]})" - } - } - expect(subject.morf(input)).to eq(expected_output) + it_behaves_like "a static morfer" do + subject do + class StaticTitleMorfer < Morfo::Base + field(:new_title).calculated{ "Static Title" } end + StaticTitleMorfer end + end - describe "#morf_single" do - it "maps calculation correctly" do - expected_output = { - title_with_channel: "#{single_input[:title]}, (#{single_input[:channel]})" - } - - expect(subject.morf_single(single_input)).to eq(expected_output) + it_behaves_like "a morfer with nested source" do + subject(:valid_path) do + class ImdbRatingMorfer < Morfo::Base + field(:rating).from(:ratings, :imdb) end + ImdbRatingMorfer end - end - context "static values" do - subject do - class StaticTitleMorfer < Morfo::Base - field(:new_title).calculated{ "Static Title" } + subject(:valid_path_with_transformation) do + class ImdbRatingMorferWithTransformation < Morfo::Base + field(:rating).from(:ratings, :imdb).transformed {|v| "Rating: #{v}"} end - StaticTitleMorfer + ImdbRatingMorferWithTransformation end - describe "#morf" do - it "maps static value correctly" do - expected_output = input.map{|r| {new_title: "Static Title"} } - expect(subject.morf(input)).to eq(expected_output) + subject(:invalid_path) do + class InvalidImdbRatingMorfer < Morfo::Base + field(:rating).from(:very, :long, :path, :that, :might, :not, :exist) end + InvalidImdbRatingMorfer end + end - describe "#morf_single" do - it "maps static value correctly" do - expected_output = { new_title: "Static Title" } - expect(subject.morf_single(single_input)).to eq(expected_output) + it_behaves_like "a morfer with nested destination" do + subject do + class WrapperMorfer < Morfo::Base + field(:tv_show, :title).from(:title) + field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} end + WrapperMorfer end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 11c6f9b..1e4cbd3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,3 +8,5 @@ require "rspec" require "morfo" + +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } diff --git a/spec/support/shared_context.rb b/spec/support/shared_context.rb new file mode 100644 index 0000000..a54436c --- /dev/null +++ b/spec/support/shared_context.rb @@ -0,0 +1,34 @@ +shared_context "tv shows" do + let(:input) do + [ + { + title: "The Walking Dead", + channel: "AMC", + watchers: 1337, + status: "running", + cast: ["Lincoln, Andrew", "McBride, Melissa"], + ratings: { + imdb: 8.7, + trakt: 89, + rotten_tomatoes: 93, + }, + }, + { + title: "Breaking Bad", + channel: "AMC", + watchers: 72891, + status: "ended", + cast: ["Cranston, Bryan", "Gunn, Anna"], + ratings: { + imdb: 9.5, + trakt: 95, + rotten_tomatoes: 100, + }, + } + ] + end + + let(:single_input) do + input.first + end +end diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb new file mode 100644 index 0000000..68ea14d --- /dev/null +++ b/spec/support/shared_examples.rb @@ -0,0 +1,227 @@ +shared_examples "an error throwing morfer" do + include_context "tv shows" + + describe "#morf" do + it "raises error for nil field" do + expect{no_from.morf([{my_field: :something}])}.to raise_error(ArgumentError) + end + end + + describe "#morf_single" do + it "raises error for nil field" do + expect{no_from.morf_single({my_field: :something})}.to raise_error(ArgumentError) + end + end +end + +shared_examples "a 1 to 1 morfer" do + include_context "tv shows" + + describe "#morf" do + let(:expected_output) { input.map{|v| {tv_show_title: v[:title]} } } + + it "maps title correctly" do + expect(subject.morf(input)).to eq(expected_output) + end + + it "leaves out nil values in result" do + expected_output = [{},{}] + modified_input = input.map{|h| h.reject{|k, v| k == :title}} + expect(subject.morf(modified_input)).to eq(expected_output) + end + end + + describe "#morf_single" do + let(:expected_output) do + { tv_show_title: single_input[:title] } + end + + it "maps title correctly" do + expect(subject.morf_single(single_input)).to eq(expected_output) + end + + it "leaves out nil values in result" do + expected_output = {} + modified_input = single_input.reject { |k, v| k == :title } + expect(subject.morf_single(modified_input)).to eq(expected_output) + end + end +end + +shared_examples "a 1 to many morfer" do + include_context "tv shows" + + describe "#morf" do + let(:expected_output) { input.map{|v| {title: v[:title], also_title: v[:title]} } } + + it "maps title to multiple fields" do + expect(subject.morf(input)).to eq(expected_output) + end + end + + describe "#morf_single" do + let(:expected_output) { {title: single_input[:title], also_title: single_input[:title]} } + + it "maps title to multiple fields" do + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end +end + +shared_examples "a 1 to 1 morfer with transformation" do + include_context "tv shows" + + describe "#morf" do + let(:expected_output) { input.map{|v| {title: "#{v[:title]} and Zombies"} } } + + it "calls transformation correctly" do + expect(subject.morf(input)).to eq(expected_output) + end + end + + describe "#morf_single" do + let(:expected_output) { { title: "#{single_input[:title]} and Zombies" } } + + it "calls transformation correctly" do + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end +end + +shared_examples "a calculating morfer" do + include_context "tv shows" + + describe "#morf" do + let(:expected_output) do + input.map{|r| { title_with_channel: "#{r[:title]}, (#{r[:channel]})" } } + end + + it "maps calculation correctly" do + expect(subject.morf(input)).to eq(expected_output) + end + end + + describe "#morf_single" do + let(:expected_output) do + { + title_with_channel: "#{single_input[:title]}, (#{single_input[:channel]})" + } + end + + it "maps calculation correctly" do + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end +end + +shared_examples "a static morfer" do + include_context "tv shows" + + describe "#morf" do + let(:expected_output) { input.map{|r| {new_title: "Static Title"} } } + + it "maps static value correctly" do + expect(subject.morf(input)).to eq(expected_output) + end + end + + describe "#morf_single" do + let(:expected_output) { { new_title: "Static Title" } } + + it "maps static value correctly" do + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end +end + +shared_examples "a morfer with nested source" do + include_context "tv shows" + + describe "#morf" do + context "valid path" do + let(:expected_output) { input.map{|v| {rating: v[:ratings][:imdb]} } } + + it "maps nested attributes correctly" do + expect(valid_path.morf(input)).to eq(expected_output) + end + end + + context "valid path with transformation" do + let(:expected_output) { input.map{|v| {rating: "Rating: #{v[:ratings][:imdb]}"} } } + + it "maps and transforms nested attributes correctly" do + expect(valid_path_with_transformation.morf(input)).to eq(expected_output) + end + end + + context "invalid path" do + let(:expected_output) { [{},{}] } + + it "doesn't raise error for invalid path" do + expect(invalid_path.morf(input)).to eq(expected_output) + end + end + end + + describe "#morf_single" do + context "valid path" do + let(:expected_output) { {rating: single_input[:ratings][:imdb]} } + + it "maps nested attributes correctly" do + expect(valid_path.morf_single(single_input)).to eq(expected_output) + end + end + + context "valid path with transformation" do + let(:expected_output) { {rating: "Rating: #{single_input[:ratings][:imdb]}"} } + + it "maps nested attributes with transformation" do + expect(valid_path_with_transformation.morf_single(single_input)).to eq(expected_output) + end + end + + context "invalid path" do + let(:expected_output) { {} } + + it "doesn't raise error for invalid path" do + expect(invalid_path.morf_single(single_input)).to eq(expected_output) + end + end + end +end + +shared_examples "a morfer with nested destination" do + include_context "tv shows" + + describe "#morf" do + let(:expected_output) do + input.map do |v| + { + tv_show: { + title: v[:title], + channel: "Channel: #{v[:channel]}", + } + } + end + end + + it "maps to nested destination" do + expect(subject.morf(input)).to eq(expected_output) + end + end + + describe "#morf_single" do + let(:expected_output) do + { + tv_show: { + title: single_input[:title], + channel: "Channel: #{single_input[:channel]}", + } + } + end + + it "maps to nested destination" do + expect(subject.morf_single(single_input)).to eq(expected_output) + end + end +end From c2101f2e8a2b4975c1710c6c95fc31503709d18c Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 22 Apr 2015 18:41:48 -0700 Subject: [PATCH 52/90] first implementation of builder --- lib/morfo.rb | 2 +- lib/morfo/builder.rb | 28 +++++++++++++++++ spec/lib/morfo/builder_spec.rb | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 lib/morfo/builder.rb create mode 100644 spec/lib/morfo/builder_spec.rb diff --git a/lib/morfo.rb b/lib/morfo.rb index b0e5f26..d5067ef 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,6 +1,6 @@ require "morfo/version" require "morfo/actions" -require "morfo/deserializer" +require "morfo/builder" module Morfo class Base diff --git a/lib/morfo/builder.rb b/lib/morfo/builder.rb new file mode 100644 index 0000000..fb145e1 --- /dev/null +++ b/lib/morfo/builder.rb @@ -0,0 +1,28 @@ +module Morfo + class Builder + def initialize(definitions) + @definitions = definitions + end + + def build + # WTF definitions is not accessable inside class + # so this javascript technique is necesseray + tmp_definitions = definitions + Class.new(Morfo::Base) do + tmp_definitions.each do |definition| + f = field(definition[:field]) + if definition[:from] + f = f.from(definition[:from]) + end + if definition[:static] + f = f.calculated { definition[:static] } + end + end + end + end + + private + + attr_reader :definitions + end +end diff --git a/spec/lib/morfo/builder_spec.rb b/spec/lib/morfo/builder_spec.rb new file mode 100644 index 0000000..da3ad13 --- /dev/null +++ b/spec/lib/morfo/builder_spec.rb @@ -0,0 +1,55 @@ +require "spec_helper" + +describe Morfo::Builder do + describe "#build" do + subject { described_class.new(definition) } + + let(:definition) do + [ + { + field: :tv_show_title, + from: :title, + } + ] + end + + let(:morfer) { subject.build } + + it "has a morf method" do + expect(morfer).to respond_to(:morf) + end + + it "has a morf_single method" do + expect(morfer).to respond_to(:morf_single) + end + end + + context "on the fly morfers" do + subject { described_class.new(definitions).build } + + it_behaves_like "a 1 to 1 morfer" do + let(:definitions) do + [ + { field: :tv_show_title, from: :title } + ] + end + end + + it_behaves_like "a 1 to many morfer" do + let(:definitions) do + [ + { field: :title, from: :title }, + { field: :also_title, from: :title }, + ] + end + end + + it_behaves_like "a static morfer" do + let(:definitions) do + [ + { field: :new_title, static: "Static Title" }, + ] + end + end + end +end From 5bbc041f582c06e4259db07d1ae79500b335d027 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 11:44:33 -0700 Subject: [PATCH 53/90] use activesupport for hash modifications --- lib/morfo.rb | 15 ++------------- morfo.gemspec | 1 + 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index d5067ef..81d4530 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,3 +1,4 @@ +require "active_support/core_ext/hash" require "morfo/version" require "morfo/actions" require "morfo/builder" @@ -17,7 +18,7 @@ def self.morf input def self.morf_single input output = {} mapping_actions.each do |field_path, action| - deep_merge!(output, store_value(action.execute(input), field_path)) + output.deep_merge!(store_value(action.execute(input), field_path)) end output end @@ -38,17 +39,5 @@ def self.store_value value, to end end end - - def self.deep_merge! hash, other_hash, &block - other_hash.each_pair do |k,v| - tv = hash[k] - if tv.is_a?(Hash) && v.is_a?(Hash) - hash[k] = deep_merge!(tv, v, &block) - else - hash[k] = block && tv ? block.call(k, tv, v) : v - end - end - hash - end end end diff --git a/morfo.gemspec b/morfo.gemspec index 7d20039..e40baaa 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.add_dependency "rake" spec.add_dependency "json" + spec.add_dependency "activesupport", ">= 3.2" spec.add_dependency "rubysl" if RUBY_ENGINE == "rbx" spec.add_development_dependency "bundler", "~> 1.3" From 247692e66d07400e9541f115550826197f1c47b7 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 11:44:53 -0700 Subject: [PATCH 54/90] add possibilty of transformation --- lib/morfo/builder.rb | 3 +++ spec/lib/morfo/builder_spec.rb | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/lib/morfo/builder.rb b/lib/morfo/builder.rb index fb145e1..ac212f3 100644 --- a/lib/morfo/builder.rb +++ b/lib/morfo/builder.rb @@ -17,6 +17,9 @@ def build if definition[:static] f = f.calculated { definition[:static] } end + if definition[:transformation] + f = f.transformed { |r| definition[:transformation] % {value: r} } + end end end end diff --git a/spec/lib/morfo/builder_spec.rb b/spec/lib/morfo/builder_spec.rb index da3ad13..d3281d6 100644 --- a/spec/lib/morfo/builder_spec.rb +++ b/spec/lib/morfo/builder_spec.rb @@ -51,5 +51,13 @@ ] end end + + it_behaves_like "a 1 to 1 morfer with transformation" do + let(:definitions) do + [ + {field: :title, from: :title, transformation: "%{value} and Zombies"} + ] + end + end end end From 07ed84b4b6234dbee54787dcbf428a7b996a14dd Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 11:46:37 -0700 Subject: [PATCH 55/90] convert to symbolized keys --- lib/morfo/builder.rb | 2 +- spec/lib/morfo/builder_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/morfo/builder.rb b/lib/morfo/builder.rb index ac212f3..0a2ba08 100644 --- a/lib/morfo/builder.rb +++ b/lib/morfo/builder.rb @@ -7,7 +7,7 @@ def initialize(definitions) def build # WTF definitions is not accessable inside class # so this javascript technique is necesseray - tmp_definitions = definitions + tmp_definitions = definitions.map { |h| h.symbolize_keys } Class.new(Morfo::Base) do tmp_definitions.each do |definition| f = field(definition[:field]) diff --git a/spec/lib/morfo/builder_spec.rb b/spec/lib/morfo/builder_spec.rb index d3281d6..fbee8a1 100644 --- a/spec/lib/morfo/builder_spec.rb +++ b/spec/lib/morfo/builder_spec.rb @@ -25,7 +25,7 @@ end context "on the fly morfers" do - subject { described_class.new(definitions).build } + subject { described_class.new(definitions).build } it_behaves_like "a 1 to 1 morfer" do let(:definitions) do From 79b132f2ddcbc7f91b5bff368258a2e8cc7d0fdf Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 12:11:42 -0700 Subject: [PATCH 56/90] add spec for nested calculations --- spec/lib/morfo_spec.rb | 7 +++++++ spec/support/shared_examples.rb | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 618a9e6..61c95dc 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -71,6 +71,13 @@ class ImdbRatingMorferWithTransformation < Morfo::Base ImdbRatingMorferWithTransformation end + subject(:valid_path_with_calculation) do + class ImdbRatingMorferWithCalculation < Morfo::Base + field(:ratings).calculated {|r| "IMDB: #{r[:ratings][:imdb]}, Trakt: #{r[:ratings][:trakt]}, Rotten Tommatoes: #{r[:ratings][:rotten_tomatoes]}" } + end + ImdbRatingMorferWithCalculation + end + subject(:invalid_path) do class InvalidImdbRatingMorfer < Morfo::Base field(:rating).from(:very, :long, :path, :that, :might, :not, :exist) diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb index 68ea14d..e95c4b7 100644 --- a/spec/support/shared_examples.rb +++ b/spec/support/shared_examples.rb @@ -154,6 +154,19 @@ end end + context "valid path with calculation" do + let(:expected_output) do + [ + { ratings: "IMDB: 8.7, Trakt: 89, Rotten Tommatoes: 93" }, + { ratings: "IMDB: 9.5, Trakt: 95, Rotten Tommatoes: 100" }, + ] + end + + it "maps nested attributes with transformation" do + expect(valid_path_with_calculation.morf(input)).to eq(expected_output) + end + end + context "invalid path" do let(:expected_output) { [{},{}] } @@ -180,6 +193,14 @@ end end + context "valid path with calculation" do + let(:expected_output) { {ratings: "IMDB: 8.7, Trakt: 89, Rotten Tommatoes: 93"} } + + it "maps nested attributes with transformation" do + expect(valid_path_with_calculation.morf_single(single_input)).to eq(expected_output) + end + end + context "invalid path" do let(:expected_output) { {} } From 9b0fe7ad1a9a86f36f2185c15cf5ef4a8fc9cbed Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 12:58:46 -0700 Subject: [PATCH 57/90] add hash flattener --- lib/morfo/tools.rb | 30 ++++++++++++ spec/lib/morfo/tools_spec.rb | 91 ++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 lib/morfo/tools.rb create mode 100644 spec/lib/morfo/tools_spec.rb diff --git a/lib/morfo/tools.rb b/lib/morfo/tools.rb new file mode 100644 index 0000000..2f2e2cf --- /dev/null +++ b/lib/morfo/tools.rb @@ -0,0 +1,30 @@ +module Morfo + module Tools + class FlattenHashKeys + attr_reader :input_hash + + def initialize(input_hash) + @input_hash = input_hash.dup.freeze + end + + def flatten + input_hash.inject({}) do |result_hash, (key, value)| + if value.is_a?(Hash) + value.each do |inner_key, inner_value| + if inner_value.is_a?(Hash) + FlattenHashKeys.new(value).flatten.each do |inner_inner_key, inner_inner_value| + result_hash.merge!("#{key}.#{inner_inner_key}".to_sym => inner_inner_value) + end + else + result_hash.merge!("#{key}.#{inner_key}".to_sym => inner_value) + end + end + else + result_hash.merge!(key.to_sym => value) + end + result_hash + end + end + end + end +end diff --git a/spec/lib/morfo/tools_spec.rb b/spec/lib/morfo/tools_spec.rb new file mode 100644 index 0000000..fde9457 --- /dev/null +++ b/spec/lib/morfo/tools_spec.rb @@ -0,0 +1,91 @@ +require "spec_helper" + +module Morfo + module Tools + describe FlattenHashKeys do + subject { described_class.new(input_hash) } + + context "symbol keys" do + context "flat hash" do + let(:input_hash) { { simple: :hash } } + let(:expected_output) { { simple: :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + + context "single nested hash" do + let(:input_hash) { { a: { nested: :hash } } } + let(:expected_output) { { :"a.nested" => :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + + context "multiple nested hash" do + let(:input_hash) { { a: { deeper: { nested: :hash } } } } + let(:expected_output) { { :"a.deeper.nested" => :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + end + + context "string keys" do + context "flat hash" do + let(:input_hash) { { "simple" => :hash } } + let(:expected_output) { { simple: :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + + context "single nested hash" do + let(:input_hash) { { "a" => { "nested" => :hash } } } + let(:expected_output) { { :"a.nested" => :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + + context "multiple nested hash" do + let(:input_hash) { { "a" => { "deeper" => { "nested" => :hash } } } } + let(:expected_output) { { :"a.deeper.nested" => :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + end + end + end +end From 216894f02edfa2a90ab5b42b987f7e8825fe2d84 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 13:00:35 -0700 Subject: [PATCH 58/90] add possibility to add calculations in nested hashes --- lib/morfo.rb | 1 + lib/morfo/builder.rb | 17 +++++++----- spec/lib/morfo/builder_spec.rb | 50 +++++++++++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 81d4530..71396ad 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,5 +1,6 @@ require "active_support/core_ext/hash" require "morfo/version" +require "morfo/tools" require "morfo/actions" require "morfo/builder" diff --git a/lib/morfo/builder.rb b/lib/morfo/builder.rb index 0a2ba08..dac38af 100644 --- a/lib/morfo/builder.rb +++ b/lib/morfo/builder.rb @@ -5,20 +5,23 @@ def initialize(definitions) end def build - # WTF definitions is not accessable inside class + # WTF??? `definitions` is not accessible inside class # so this javascript technique is necesseray tmp_definitions = definitions.map { |h| h.symbolize_keys } Class.new(Morfo::Base) do tmp_definitions.each do |definition| - f = field(definition[:field]) + f = field(*definition[:field]) + if definition[:from] - f = f.from(definition[:from]) + f = f.from(*definition[:from]) end - if definition[:static] - f = f.calculated { definition[:static] } + + if definition[:calculated] + f = f.calculated { |r| definition[:calculated] % Morfo::Tools::FlattenHashKeys.new(r).flatten } end - if definition[:transformation] - f = f.transformed { |r| definition[:transformation] % {value: r} } + + if definition[:transformed] + f = f.transformed { |v| definition[:transformed] % {value: v} } end end end diff --git a/spec/lib/morfo/builder_spec.rb b/spec/lib/morfo/builder_spec.rb index fbee8a1..a40be56 100644 --- a/spec/lib/morfo/builder_spec.rb +++ b/spec/lib/morfo/builder_spec.rb @@ -2,9 +2,9 @@ describe Morfo::Builder do describe "#build" do - subject { described_class.new(definition) } + subject { described_class.new(definitions) } - let(:definition) do + let(:definitions) do [ { field: :tv_show_title, @@ -47,7 +47,15 @@ it_behaves_like "a static morfer" do let(:definitions) do [ - { field: :new_title, static: "Static Title" }, + { field: :new_title, calculated: "Static Title" }, + ] + end + end + + it_behaves_like "a calculating morfer" do + let(:definitions) do + [ + { field: :title_with_channel, calculated: "%{title}, (%{channel})" }, ] end end @@ -55,9 +63,43 @@ it_behaves_like "a 1 to 1 morfer with transformation" do let(:definitions) do [ - {field: :title, from: :title, transformation: "%{value} and Zombies"} + {field: :title, from: :title, transformed: "%{value} and Zombies"} ] end end + + it_behaves_like "a morfer with nested source" do + subject(:valid_path) do + described_class.new( + [ + { field: :rating, from: [:ratings, :imdb] } + ] + ).build + end + + subject(:valid_path_with_transformation) do + described_class.new( + [ + { field: :rating, from: [:ratings, :imdb], transformed: "Rating: %{value}" } + ] + ).build + end + + subject(:valid_path_with_calculation) do + described_class.new( + [ + { field: :ratings, calculated: "IMDB: %{ratings.imdb}, Trakt: %{ratings.trakt}, Rotten Tommatoes: %{ratings.rotten_tomatoes}" } + ] + ).build + end + + subject(:invalid_path) do + described_class.new( + [ + { field: :rating, from: [:very, :long, :path, :that, :might, :not, :exist] } + ] + ).build + end + end end end From de63079168df228d29cd7886a2360a0d45313aef Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 13:10:19 -0700 Subject: [PATCH 59/90] slightly improve readability of hash merging --- lib/morfo/tools.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/morfo/tools.rb b/lib/morfo/tools.rb index 2f2e2cf..1ddbb54 100644 --- a/lib/morfo/tools.rb +++ b/lib/morfo/tools.rb @@ -9,20 +9,24 @@ def initialize(input_hash) def flatten input_hash.inject({}) do |result_hash, (key, value)| + inner_hash = false if value.is_a?(Hash) + inner_hash = true value.each do |inner_key, inner_value| if inner_value.is_a?(Hash) - FlattenHashKeys.new(value).flatten.each do |inner_inner_key, inner_inner_value| - result_hash.merge!("#{key}.#{inner_inner_key}".to_sym => inner_inner_value) - end - else - result_hash.merge!("#{key}.#{inner_key}".to_sym => inner_value) + inner_hash = true end + result_hash.merge!("#{key}.#{inner_key}".to_sym => inner_value) end else result_hash.merge!(key.to_sym => value) end - result_hash + + if inner_hash + FlattenHashKeys.new(result_hash).flatten + else + result_hash + end end end end From 7b808ca50fa86e103fefd477b5b6e8df4e13d82e Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 15:24:59 -0700 Subject: [PATCH 60/90] add spec for nested destination --- spec/lib/morfo/builder_spec.rb | 9 +++++++++ spec/lib/morfo_spec.rb | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/spec/lib/morfo/builder_spec.rb b/spec/lib/morfo/builder_spec.rb index a40be56..a04ec90 100644 --- a/spec/lib/morfo/builder_spec.rb +++ b/spec/lib/morfo/builder_spec.rb @@ -101,5 +101,14 @@ ).build end end + + it_behaves_like "a morfer with nested destination" do + let(:definitions) do + [ + { field: [:tv_show, :title], from: :title }, + { field: [:tv_show, :channel], from: :channel, transformed: "Channel: %{value}" }, + ] + end + end end end diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 61c95dc..2bd46be 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -95,4 +95,14 @@ class WrapperMorfer < Morfo::Base WrapperMorfer end end + + it_behaves_like "a morfer with nested destination" do + subject do + class WrapperMorfer < Morfo::Base + field(:tv_show, :title).from(:title) + field(:tv_show, :channel).from(:channel).transformed {|v| "Channel: #{v}"} + end + WrapperMorfer + end + end end From 12f881e62c16e3c17344fee94a1fc3d746a1e551 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 15:39:55 -0700 Subject: [PATCH 61/90] format --- spec/lib/morfo/builder_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/morfo/builder_spec.rb b/spec/lib/morfo/builder_spec.rb index a04ec90..efb4594 100644 --- a/spec/lib/morfo/builder_spec.rb +++ b/spec/lib/morfo/builder_spec.rb @@ -63,7 +63,7 @@ it_behaves_like "a 1 to 1 morfer with transformation" do let(:definitions) do [ - {field: :title, from: :title, transformed: "%{value} and Zombies"} + { field: :title, from: :title, transformed: "%{value} and Zombies" } ] end end From 12a9015b9fbce5aaecc36bf72a7e73debb994ecc Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 16:14:12 -0700 Subject: [PATCH 62/90] correct headlines --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 52d1119..b52fa2a 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ If you want to have access to nested values, just provide the path to that field # {first_name: "Bruce", last_name: "Wayne"}, # ] -## Transformations +### Transformations It's also possible to transform the value in any way ruby lets you transform a value. just provide a block in the `transformed` method. @@ -94,7 +94,7 @@ It's also possible to transform the value in any way ruby lets you transform a v # {title: "Fifty Shades of Grey and Zombies"}, # ] -## Calculations +### Calculations If the value of your field should be based on multiple fields of the input row, yoy can specify a calculation block via the `calculated` method. As an argument the whole input row is passed in. From 68d111cd0cff34db53f7c30a8a8f5d7d06032f70 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 16:16:52 -0700 Subject: [PATCH 63/90] add ruby syntax highlighting to README --- README.md | 131 +++++++++++++++++++++++++++++------------------------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index b52fa2a..264d365 100644 --- a/README.md +++ b/README.md @@ -32,83 +32,92 @@ Use the `field` method to specify what fields exist and where they will get thei The most basic form is copying the value from another field. - class Title < Morfo::Base - field(:tv_show_title)from(:title) - end +```ruby +class Title < Morfo::Base + field(:tv_show_title)from(:title) +end +``` Afterwards use the `morf` method to morf all hashes in one array to the end result: - Title.morf([ - {title: "The Walking Dead"} , - {title: "Breaking Bad"}, - ]) +```ruby +Title.morf([ + {title: "The Walking Dead"} , + {title: "Breaking Bad"}, + ]) - # [ - # {tv_show_title: "The Walking Dead"}, - # {tv_show_title: "Breaking Bad"}, - # ] +# [ +# {tv_show_title: "The Walking Dead"}, +# {tv_show_title: "Breaking Bad"}, +# ] +``` If you want to have access to nested values, just provide the path to that field comma separated. - - class Name < Morfo::Base - field(:first_name).from(:name, :first) - field(:last_name).from(:name, :last) - end - - Name.morf([ - { - name: { - first: "Clark", - last: "Kent", - }, - }, - { - name: { - first: "Bruce", - last: "Wayne", - }, - }, - ]) - - # [ - # {first_name: "Clark", last_name: "Kent"}, - # {first_name: "Bruce", last_name: "Wayne"}, - # ] +```ruby +class Name < Morfo::Base + field(:first_name).from(:name, :first) + field(:last_name).from(:name, :last) +end + +Name.morf([ + { + name: { + first: "Clark", + last: "Kent", + }, + }, + { + name: { + first: "Bruce", + last: "Wayne", + }, + }, + ]) + +# [ +# {first_name: "Clark", last_name: "Kent"}, +# {first_name: "Bruce", last_name: "Wayne"}, +# ] +``` ### Transformations It's also possible to transform the value in any way ruby lets you transform a value. just provide a block in the `transformed` method. - class AndZombies < Morfo::Base - field(:title).from(title).transformed {|title| "#{title} and Zombies"} - end +```ruby +class AndZombies < Morfo::Base + field(:title).from(title).transformed {|title| "#{title} and Zombies"} +end - AndZombies.morf([ - {title: "Pride and Prejudice"}, - {title: "Fifty Shades of Grey"}, - ]) +AndZombies.morf([ + {title: "Pride and Prejudice"}, + {title: "Fifty Shades of Grey"}, + ]) - # [ - # {title: "Pride and Prejudice and Zombies"}, - # {title: "Fifty Shades of Grey and Zombies"}, - # ] +# [ +# {title: "Pride and Prejudice and Zombies"}, +# {title: "Fifty Shades of Grey and Zombies"}, +# ] +``` ### Calculations If the value of your field should be based on multiple fields of the input row, yoy can specify a calculation block via the `calculated` method. As an argument the whole input row is passed in. - class NameConcatenator < Morfo::Base - field(:name).calculated {|row| "#{row[:first_name]} #{row[:last_name]}"} - field(:status).calculated {"Best Friend"} - end - - NameConcatenator.morf([ - {first_name: "Robin", last_name: "Hood"}, - {first_name: "Sherlock", last_name: "Holmes"}, - ]) - - # [ - # {:name=>"Robin Hood", :status=>"Best Friend"}, - # {:name=>"Sherlock Holmes", :status=>"Best Friend"} - # ] +```ruby +class NameConcatenator < Morfo::Base + field(:name).calculated {|row| "#{row[:first_name]} #{row[:last_name]}"} + field(:status).calculated {"Best Friend"} +end + +NameConcatenator.morf([ + {first_name: "Robin", last_name: "Hood"}, + {first_name: "Sherlock", last_name: "Holmes"}, + ]) + +# [ +# {:name=>"Robin Hood", :status=>"Best Friend"}, +# {:name=>"Sherlock Holmes", :status=>"Best Friend"} +# ] +``` From f273fc5f2dbeb6324a3fdce350e180a859504dbb Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 16:37:15 -0700 Subject: [PATCH 64/90] add section about builders --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/README.md b/README.md index 264d365..2061f80 100644 --- a/README.md +++ b/README.md @@ -121,3 +121,110 @@ NameConcatenator.morf([ # {:name=>"Sherlock Holmes", :status=>"Best Friend"} # ] ``` + +### Builder + +On top of creating transformers with Ruby classes, it is also possible to build transformers with a hash syntax (which could then be serialized as json and stored somewhere else). + +```ruby +morfer = Morfo::Builder.new([ + { field: :first_name, from: [:name, :first] }, + { field: :last_name, from: [:name, :last] }, +]) + +morfer.morf([ + { + name: { + first: "Clark", + last: "Kent", + }, + }, + { + name: { + first: "Bruce", + last: "Wayne", + }, + }, + ]) + +# [ +# { first_name: "Clark", last_name: "Kent" }, +# { first_name: "Bruce", last_name: "Wayne" }, +# ] +``` + +The builder includes all other features such as calculation and transformation + +#### Builder Transformations + +To transform a value, use the placeholder %{value} + +```ruby +class AndZombies < Morfo::Base + field(:title).from(title).transformed {|title| "#{title} and Zombies"} +end + +morfer = Morfo::Builder.new([ + { field: :title, from: :title, transformed: "%{title} and Zombies" }, +]) + +morfer.morf([ + {title: "Pride and Prejudice"}, + {title: "Fifty Shades of Grey"}, + ]) + +# [ +# { title: "Pride and Prejudice and Zombies" }, +# { title: "Fifty Shades of Grey and Zombies" }, +# ] +``` + +#### Builder Calculations + +To get access to the other fields use the [ruby string format syntax](http://ruby-doc.org/core-2.2.0/String.html#method-i-25). + +```ruby +morfer = Morfo::Builder.new([ + { field: :name, calculated: "%{first_name} %{last_name}" }, + { field: :status, calculated: "Best Friend" }, +]) + +morfer.morf([ + {first_name: "Robin", last_name: "Hood"}, + {first_name: "Sherlock", last_name: "Holmes"}, +]) + +# [ +# { name: "Robin Hood", status: "Best Friend" }, +# { name: "Sherlock Holmes", status: "Best Friend" } +# ] +``` + +It's even possible to get access to nested keys, using a dot as separator: + +```ruby +morfer = Morfo::Builder.new([ + { field: :first_name, calculated: "%{name.first} %{name.last}" }, +]) + +morfer.morf([ + { + name: { + first: "Clark", + last: "Kent", + }, + }, + { + name: { + first: "Bruce", + last: "Wayne", + }, + }, + ]) + +# [ +# { name: "Clark Kent" }, +# { name: "Bruce Wayne" }, +# ] +``` + From f7f19908b97bc2be5609dc83f407c86900fb687e Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Thu, 23 Apr 2015 16:45:06 -0700 Subject: [PATCH 65/90] beautify README --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 2061f80..f307082 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,13 @@ Afterwards use the `morf` method to morf all hashes in one array to the end resu ```ruby Title.morf([ - {title: "The Walking Dead"} , - {title: "Breaking Bad"}, + { title: "The Walking Dead" }, + { title: "Breaking Bad" }, ]) # [ -# {tv_show_title: "The Walking Dead"}, -# {tv_show_title: "Breaking Bad"}, +# { tv_show_title: "The Walking Dead" }, +# { tv_show_title: "Breaking Bad" }, # ] ``` @@ -76,8 +76,8 @@ Name.morf([ ]) # [ -# {first_name: "Clark", last_name: "Kent"}, -# {first_name: "Bruce", last_name: "Wayne"}, +# { first_name: "Clark", last_name: "Kent" }, +# { first_name: "Bruce", last_name: "Wayne" }, # ] ``` @@ -91,13 +91,13 @@ class AndZombies < Morfo::Base end AndZombies.morf([ - {title: "Pride and Prejudice"}, - {title: "Fifty Shades of Grey"}, + { title: "Pride and Prejudice" }, + { title: "Fifty Shades of Grey" }, ]) # [ -# {title: "Pride and Prejudice and Zombies"}, -# {title: "Fifty Shades of Grey and Zombies"}, +# { title: "Pride and Prejudice and Zombies" }, +# { title: "Fifty Shades of Grey and Zombies" }, # ] ``` @@ -112,13 +112,13 @@ class NameConcatenator < Morfo::Base end NameConcatenator.morf([ - {first_name: "Robin", last_name: "Hood"}, - {first_name: "Sherlock", last_name: "Holmes"}, + { first_name: "Robin", last_name: "Hood" }, + { first_name: "Sherlock", last_name: "Holmes" }, ]) # [ -# {:name=>"Robin Hood", :status=>"Best Friend"}, -# {:name=>"Sherlock Holmes", :status=>"Best Friend"} +# { name: "Robin Hood", status: "Best Friend" }, +# { name: "Sherlock Holmes", status: "Best Friend" } # ] ``` @@ -169,8 +169,8 @@ morfer = Morfo::Builder.new([ ]) morfer.morf([ - {title: "Pride and Prejudice"}, - {title: "Fifty Shades of Grey"}, + { title: "Pride and Prejudice" }, + { title: "Fifty Shades of Grey" }, ]) # [ @@ -190,8 +190,8 @@ morfer = Morfo::Builder.new([ ]) morfer.morf([ - {first_name: "Robin", last_name: "Hood"}, - {first_name: "Sherlock", last_name: "Holmes"}, + { first_name: "Robin", last_name: "Hood" }, + { first_name: "Sherlock", last_name: "Holmes" }, ]) # [ @@ -204,7 +204,7 @@ It's even possible to get access to nested keys, using a dot as separator: ```ruby morfer = Morfo::Builder.new([ - { field: :first_name, calculated: "%{name.first} %{name.last}" }, + { field: :name, calculated: "%{name.first} %{name.last}" }, ]) morfer.morf([ @@ -220,7 +220,7 @@ morfer.morf([ last: "Wayne", }, }, - ]) +]) # [ # { name: "Clark Kent" }, From 66d3039f759dc1b68b3c30e7ac828481c0d7187c Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 27 Apr 2015 15:55:15 -0700 Subject: [PATCH 66/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index c835bed..8d697c5 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = "0.3.0" + VERSION = "0.4.0" end From e02eda56ff2691337f2b353507941a28f44cfcc3 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 6 May 2015 16:35:01 -0700 Subject: [PATCH 67/90] remove benchmarks from gem --- morfo.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morfo.gemspec b/morfo.gemspec index e40baaa..7c9274c 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.test_files = spec.files.grep(%r{^(test|spec|features|benchmarks)/}) spec.require_paths = ["lib"] spec.add_dependency "rake" From c100dc47548e19db1ff380820c966f844b691d71 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 11 Sep 2015 10:13:15 -0700 Subject: [PATCH 68/90] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f307082..eb2006a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The most basic form is copying the value from another field. ```ruby class Title < Morfo::Base - field(:tv_show_title)from(:title) + field(:tv_show_title).from(:title) end ``` From 949e80ba1e0964dffee45d3b07d0e03dc26b843c Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 11 Sep 2015 15:12:25 -0700 Subject: [PATCH 69/90] add possibility to include nil values --- lib/morfo.rb | 14 ++++++------- spec/support/shared_examples.rb | 36 +++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index 71396ad..0ce442f 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -12,14 +12,14 @@ def self.field *field_path act end - def self.morf input - input.map { |row| morf_single(row) } + def self.morf input, options = {} + input.map { |row| morf_single(row, options) } end - def self.morf_single input + def self.morf_single input, options = {} output = {} mapping_actions.each do |field_path, action| - output.deep_merge!(store_value(action.execute(input), field_path)) + output.deep_merge!(store_value(action.execute(input), field_path, options)) end output end @@ -29,11 +29,11 @@ def self.mapping_actions @actions ||= {} end - def self.store_value value, to - return {} if value.nil? + def self.store_value value, to, options + return {} if value.nil? && !options[:include_nil_values] to.reverse.inject({}) do |hash, key| - if hash.keys.first.nil? + if hash.empty? hash.merge!(key => value) else { key => hash } diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb index e95c4b7..5fd8097 100644 --- a/spec/support/shared_examples.rb +++ b/spec/support/shared_examples.rb @@ -24,10 +24,20 @@ expect(subject.morf(input)).to eq(expected_output) end - it "leaves out nil values in result" do - expected_output = [{},{}] - modified_input = input.map{|h| h.reject{|k, v| k == :title}} - expect(subject.morf(modified_input)).to eq(expected_output) + context "include nils" do + it "includes nil values in result" do + expected_output = [{tv_show_title: nil},{tv_show_title: nil}] + modified_input = input.map{|h| h.reject{|k, v| k == :title}} + expect(subject.morf(modified_input, {include_nil_values: true})).to eq(expected_output) + end + end + + context "disable nils" do + it "leaves out nil values in result" do + expected_output = [{},{}] + modified_input = input.map{|h| h.reject{|k, v| k == :title}} + expect(subject.morf(modified_input)).to eq(expected_output) + end end end @@ -40,10 +50,20 @@ expect(subject.morf_single(single_input)).to eq(expected_output) end - it "leaves out nil values in result" do - expected_output = {} - modified_input = single_input.reject { |k, v| k == :title } - expect(subject.morf_single(modified_input)).to eq(expected_output) + context "include nils" do + it "includes nil values in result" do + expected_output = {tv_show_title: nil} + modified_input = single_input.reject { |k, v| k == :title } + expect(subject.morf_single(modified_input, {include_nil_values: true})).to eq(expected_output) + end + end + + context "disable nils" do + it "leaves out nil values in result" do + expected_output = {} + modified_input = single_input.reject { |k, v| k == :title } + expect(subject.morf_single(modified_input)).to eq(expected_output) + end end end end From b6120b2c02a3671dd33ac95cb8eec1e37873a40c Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 11 Sep 2015 15:18:04 -0700 Subject: [PATCH 70/90] document `include_nil_values` --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index eb2006a..57e88da 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,21 @@ In order to morf the hashes you have to provide a class that extends `Morf::Base Use the `field` method to specify what fields exist and where they will get their data from: +### Options + +The following options can be passed to the morfer + +#### include_nil_values + +When passed in, all values that can not be found in the source hash will be set to nil (defaults to `false`). By default those keys will just appear in the result: + +```ruby +Title.morf([ + { title: "The Walking Dead" }, + { title: "Breaking Bad" }, + ], include_nil_values: true) +``` + ### Simple Mapping The most basic form is copying the value from another field. From 75d41ad08914635a35c7202745cea3cd41ceb16c Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 11 Sep 2015 15:39:32 -0700 Subject: [PATCH 71/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 8d697c5..6b024b9 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = "0.4.0" + VERSION = "0.5.0" end From 549766e4c762f37342c66e17bb7aebc415a1405a Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 26 Oct 2015 14:57:41 -0500 Subject: [PATCH 72/90] add tools for creating base keys --- lib/morfo/tools.rb | 16 +++++++++++ spec/lib/morfo/tools_spec.rb | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/lib/morfo/tools.rb b/lib/morfo/tools.rb index 1ddbb54..1a7da3d 100644 --- a/lib/morfo/tools.rb +++ b/lib/morfo/tools.rb @@ -30,5 +30,21 @@ def flatten end end end + + class BaseKeys + attr_reader :input_string + + def initialize(input_string) + @input_string = input_string.nil? ? "" : input_string.dup.freeze + end + + def build + keys = input_string.scan(/\%\{([^\}]+)\}/).flatten + + keys.inject({}) do |hash, key| + hash.merge!(key.to_sym => nil) + end + end + end end end diff --git a/spec/lib/morfo/tools_spec.rb b/spec/lib/morfo/tools_spec.rb index fde9457..a3ed216 100644 --- a/spec/lib/morfo/tools_spec.rb +++ b/spec/lib/morfo/tools_spec.rb @@ -32,6 +32,19 @@ module Tools end end + context "already nested" do + let(:input_hash) { { :"a.nested" => :hash } } + let(:expected_output) { { :"a.nested" => :hash } } + + it "returns a new hash" do + expect(subject.flatten).not_to be(input_hash) + end + + it "returns the correct hash" do + expect(subject.flatten).to eq(expected_output) + end + end + context "multiple nested hash" do let(:input_hash) { { a: { deeper: { nested: :hash } } } } let(:expected_output) { { :"a.deeper.nested" => :hash } } @@ -87,5 +100,44 @@ module Tools end end end + + describe BaseKeys do + subject { described_class.new(input_string) } + + describe "#build" do + context "empty string" do + let(:input_string) { "" } + let(:expected_output) { { } } + + context "empty string" do + it "returns empty hash" do + expect(subject.build).to eq(expected_output) + end + end + end + + context "nil input" do + let(:input_string) { nil } + let(:expected_output) { { } } + + context "empty string" do + it "returns empty hash" do + expect(subject.build).to eq(expected_output) + end + end + end + + context "non nested values" do + let(:input_string) { "The rating is: %{rating}" } + let(:expected_output) { { rating: nil } } + + describe "build" do + it "returns expected values" do + expect(subject.build).to eq(expected_output) + end + end + end + end + end end end From 2f90f86618fb9d043b78b3a9115e38d2a6e688f3 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 26 Oct 2015 14:58:27 -0500 Subject: [PATCH 73/90] merge all existing keys to calculated row --- lib/morfo/builder.rb | 3 ++- spec/lib/morfo_spec.rb | 2 +- spec/support/shared_examples.rb | 36 ++++++++++++++++++++++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/morfo/builder.rb b/lib/morfo/builder.rb index dac38af..42c8b90 100644 --- a/lib/morfo/builder.rb +++ b/lib/morfo/builder.rb @@ -17,7 +17,8 @@ def build end if definition[:calculated] - f = f.calculated { |r| definition[:calculated] % Morfo::Tools::FlattenHashKeys.new(r).flatten } + base_hash = Morfo::Tools::BaseKeys.new(definition[:calculated]).build + f = f.calculated { |r| definition[:calculated] % base_hash.merge(Morfo::Tools::FlattenHashKeys.new(r).flatten) } end if definition[:transformed] diff --git a/spec/lib/morfo_spec.rb b/spec/lib/morfo_spec.rb index 2bd46be..a38dead 100644 --- a/spec/lib/morfo_spec.rb +++ b/spec/lib/morfo_spec.rb @@ -73,7 +73,7 @@ class ImdbRatingMorferWithTransformation < Morfo::Base subject(:valid_path_with_calculation) do class ImdbRatingMorferWithCalculation < Morfo::Base - field(:ratings).calculated {|r| "IMDB: #{r[:ratings][:imdb]}, Trakt: #{r[:ratings][:trakt]}, Rotten Tommatoes: #{r[:ratings][:rotten_tomatoes]}" } + field(:ratings).calculated {|r| "IMDB: #{r[:ratings] && r[:ratings][:imdb]}, Trakt: #{r[:ratings] && r[:ratings][:trakt]}, Rotten Tommatoes: #{r[:ratings] && r[:ratings][:rotten_tomatoes]}" } end ImdbRatingMorferWithCalculation end diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb index 5fd8097..4ab436e 100644 --- a/spec/support/shared_examples.rb +++ b/spec/support/shared_examples.rb @@ -175,15 +175,37 @@ end context "valid path with calculation" do - let(:expected_output) do - [ - { ratings: "IMDB: 8.7, Trakt: 89, Rotten Tommatoes: 93" }, - { ratings: "IMDB: 9.5, Trakt: 95, Rotten Tommatoes: 100" }, - ] + context "all inputs matched" do + let(:expected_output) do + [ + { ratings: "IMDB: 8.7, Trakt: 89, Rotten Tommatoes: 93" }, + { ratings: "IMDB: 9.5, Trakt: 95, Rotten Tommatoes: 100" }, + ] + end + + it "maps nested attributes with transformation" do + expect(valid_path_with_calculation.morf(input)).to eq(expected_output) + end end - it "maps nested attributes with transformation" do - expect(valid_path_with_calculation.morf(input)).to eq(expected_output) + context "inputs not matched" do + let(:expected_output) do + [ + { ratings: "IMDB: , Trakt: , Rotten Tommatoes: " }, + { ratings: "IMDB: , Trakt: , Rotten Tommatoes: " }, + ] + end + + let(:modified_input) do + input.map do |row| + row.delete(:ratings) + row + end + end + + it "maps nested attributes with transformation" do + expect(valid_path_with_calculation.morf(modified_input)).to eq(expected_output) + end end end From 8d4a72404305b72c949a0837a5ef5e16f1d80997 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 26 Oct 2015 15:17:00 -0500 Subject: [PATCH 74/90] upgrade travis rubies --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 492b26b..72c6363 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,13 @@ cache: bundler env: JRUBY_OPTS=--2.0 rvm: - 2.0.0 - - 2.1.3 - - jruby-1.7.16 + - 2.1.7 + - 2.2.3 + - jruby-1.7.22 + - jruby-9.0.3.0 - ruby-head - jruby-head - - rbx-2.2.3 + - rbx-2.2.9 matrix: fast_finish: true allow_failures: From e1de12dd6983bc2c25c1204ba38decc0752046fd Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 26 Oct 2015 15:45:05 -0500 Subject: [PATCH 75/90] move to docker infrastructure --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 72c6363..752674f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: ruby cache: bundler env: JRUBY_OPTS=--2.0 From 9cdea682e89c8885cd78b49fef48b4e51a80f92d Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 26 Oct 2015 16:19:04 -0500 Subject: [PATCH 76/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 6b024b9..80dcf17 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = "0.5.0" + VERSION = "0.5.1" end From a40a78ccf9411d72f3ded8d032a52cb81f5198eb Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Tue, 2 Feb 2016 12:47:01 +0100 Subject: [PATCH 77/90] fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57e88da..6d71330 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ class AndZombies < Morfo::Base end morfer = Morfo::Builder.new([ - { field: :title, from: :title, transformed: "%{title} and Zombies" }, + { field: :title, from: :title, transformed: "%{value} and Zombies" }, ]) morfer.morf([ From 6c8b7db385559b7ad8c831a6a5bf6bc06f56bab5 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Tue, 2 Feb 2016 14:23:14 +0100 Subject: [PATCH 78/90] create custom error class to make it easier to identify errors --- lib/morfo.rb | 1 + lib/morfo/actions.rb | 2 +- lib/morfo/errors.rb | 4 ++++ spec/support/shared_examples.rb | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 lib/morfo/errors.rb diff --git a/lib/morfo.rb b/lib/morfo.rb index 0ce442f..f6d20ff 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -3,6 +3,7 @@ require "morfo/tools" require "morfo/actions" require "morfo/builder" +require "morfo/errors" module Morfo class Base diff --git a/lib/morfo/actions.rb b/lib/morfo/actions.rb index 757d224..24ecd02 100644 --- a/lib/morfo/actions.rb +++ b/lib/morfo/actions.rb @@ -22,7 +22,7 @@ def calculated &calculate_blk end def execute row - raise ArgumentError, + raise Morfo::NoSourceFieldError, "No field to get value from is specified for #{field_path.inspect}" end end diff --git a/lib/morfo/errors.rb b/lib/morfo/errors.rb new file mode 100644 index 0000000..94ecb0e --- /dev/null +++ b/lib/morfo/errors.rb @@ -0,0 +1,4 @@ +module Morfo + class StandardError < ::StandardError; end + class NoSourceFieldError < StandardError; end +end diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb index 4ab436e..492b956 100644 --- a/spec/support/shared_examples.rb +++ b/spec/support/shared_examples.rb @@ -3,13 +3,13 @@ describe "#morf" do it "raises error for nil field" do - expect{no_from.morf([{my_field: :something}])}.to raise_error(ArgumentError) + expect{no_from.morf([{my_field: :something}])}.to raise_error(Morfo::NoSourceFieldError) end end describe "#morf_single" do it "raises error for nil field" do - expect{no_from.morf_single({my_field: :something})}.to raise_error(ArgumentError) + expect{no_from.morf_single({my_field: :something})}.to raise_error(Morfo::NoSourceFieldError) end end end From 16cf4b419949bda4549b81613c114b10e93bbaf1 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Tue, 2 Feb 2016 14:33:24 +0100 Subject: [PATCH 79/90] prepare for rails 5.0 --- morfo.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morfo.gemspec b/morfo.gemspec index 7c9274c..abdb5aa 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.add_dependency "rake" spec.add_dependency "json" - spec.add_dependency "activesupport", ">= 3.2" + spec.add_dependency "activesupport", "< 5.1" spec.add_dependency "rubysl" if RUBY_ENGINE == "rbx" spec.add_development_dependency "bundler", "~> 1.3" From fa2f9e92fda9ff8c7ae222529572daa1aa5a45ca Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Tue, 2 Feb 2016 14:51:44 +0100 Subject: [PATCH 80/90] improve travis.yml --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 752674f..366319d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,22 @@ sudo: false language: ruby cache: bundler -env: JRUBY_OPTS=--2.0 rvm: - 2.0.0 - - 2.1.7 - - 2.2.3 - - jruby-1.7.22 - - jruby-9.0.3.0 + - 2.1.8 + - 2.2.4 + - 2.3.0 + - jruby-9.0.5.0 - ruby-head - jruby-head - - rbx-2.2.9 + - rbx-2.11 + - rbx-3.14 matrix: fast_finish: true allow_failures: - rvm: ruby-head - rvm: jruby-head - - rvm: rbx-2.2.3 + - rvm: rbx-2.11 + - rvm: rbx-3.14 script: - - bundle exec rake spec + - bundle exec rspec From b05ea8b4134b920bb2777ee0ca6c369bd39109da Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Tue, 2 Feb 2016 14:56:00 +0100 Subject: [PATCH 81/90] release 0.6.0 --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 80dcf17..8fe2ab1 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = "0.5.1" + VERSION = "0.6.0" end From d7c5d78ce8f7ad0b3c271c43fcff21e9a70f5bdf Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 31 Mar 2017 14:57:41 +0200 Subject: [PATCH 82/90] reconcile dependencies make sure the gem still works on ruby 2.0 --- .travis.yml | 2 ++ Gemfile | 10 ++++++---- morfo.gemspec | 10 +++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 366319d..3bf09bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,13 @@ sudo: false language: ruby cache: bundler +bundler_args: --without development rvm: - 2.0.0 - 2.1.8 - 2.2.4 - 2.3.0 + - 2.4.0 - jruby-9.0.5.0 - ruby-head - jruby-head diff --git a/Gemfile b/Gemfile index c07214c..a68a905 100644 --- a/Gemfile +++ b/Gemfile @@ -4,16 +4,18 @@ source "https://rubygems.org" gemspec group :test, :development do + gem "rspec", ">= 2.14", "< 4.0" gem "coveralls", require: false + gem "simplecov" + gem "rubinius-coverage", platform: :rbx +end + +group :development do gem "guard" gem "guard-rspec" - gem "simplecov" gem "pry" - gem "rubinius-coverage", platform: :rbx gem "rb-inotify", require: false gem "rb-fsevent", require: false gem "rb-fchange", require: false end - -gem "json" diff --git a/morfo.gemspec b/morfo.gemspec index abdb5aa..3c4c43c 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features|benchmarks)/}) spec.require_paths = ["lib"] + spec.required_ruby_version = '>= 2.0.0' - spec.add_dependency "rake" - spec.add_dependency "json" - spec.add_dependency "activesupport", "< 5.1" - spec.add_dependency "rubysl" if RUBY_ENGINE == "rbx" + spec.add_runtime_dependency "json" + spec.add_runtime_dependency "activesupport", "< 5.1" + spec.add_runtime_dependency "rubysl" if RUBY_ENGINE == "rbx" + spec.add_development_dependency "rake" spec.add_development_dependency "bundler", "~> 1.3" - spec.add_development_dependency "rspec", ">= 2.14", "< 4.0" end From a3e6801f0ace3651a80bcfdbc5776fba86aceefc Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 31 Mar 2017 15:20:40 +0200 Subject: [PATCH 83/90] downgrade activesupport just to make the tests pass --- morfo.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morfo.gemspec b/morfo.gemspec index 3c4c43c..6bbf8f3 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.0.0' spec.add_runtime_dependency "json" - spec.add_runtime_dependency "activesupport", "< 5.1" + spec.add_runtime_dependency "activesupport", "< 5.0" spec.add_runtime_dependency "rubysl" if RUBY_ENGINE == "rbx" spec.add_development_dependency "rake" From e72b696d2bf8e906cb04c6873670a3dba1967c69 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Wed, 29 Mar 2017 15:18:44 +0200 Subject: [PATCH 84/90] change email address --- morfo.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morfo.gemspec b/morfo.gemspec index 6bbf8f3..53a83c3 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |spec| spec.name = "morfo" spec.version = Morfo::VERSION spec.authors = ["Leif Gensert"] - spec.email = ["leifg@gmx.de"] + spec.email = ["leif@leif.io"] spec.description = %q{This gem provides a DSL for converting one hash into another} spec.summary = %q{Inspired by ActiveImporter, this gem generically converts an array of hashes} spec.homepage = "" From c509e5f8162875354a8043d987119903960020ef Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 31 Mar 2017 16:20:05 +0200 Subject: [PATCH 85/90] completely get rid of activesupport use deep_merge gem for deep_merge and backport symbolize_keys --- lib/morfo.rb | 4 ++-- lib/morfo/builder.rb | 2 +- lib/morfo/tools.rb | 21 +++++++++++++++++++-- morfo.gemspec | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/morfo.rb b/lib/morfo.rb index f6d20ff..e1cecc1 100644 --- a/lib/morfo.rb +++ b/lib/morfo.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/hash" +require 'deep_merge/rails_compat' require "morfo/version" require "morfo/tools" require "morfo/actions" @@ -20,7 +20,7 @@ def self.morf input, options = {} def self.morf_single input, options = {} output = {} mapping_actions.each do |field_path, action| - output.deep_merge!(store_value(action.execute(input), field_path, options)) + output.deeper_merge!(store_value(action.execute(input), field_path, options)) end output end diff --git a/lib/morfo/builder.rb b/lib/morfo/builder.rb index 42c8b90..e4ef994 100644 --- a/lib/morfo/builder.rb +++ b/lib/morfo/builder.rb @@ -7,7 +7,7 @@ def initialize(definitions) def build # WTF??? `definitions` is not accessible inside class # so this javascript technique is necesseray - tmp_definitions = definitions.map { |h| h.symbolize_keys } + tmp_definitions = definitions.map { |h| Morfo::Tools::ExtendedHash.symbolize_keys(h) } Class.new(Morfo::Base) do tmp_definitions.each do |definition| f = field(*definition[:field]) diff --git a/lib/morfo/tools.rb b/lib/morfo/tools.rb index 1a7da3d..a68c67d 100644 --- a/lib/morfo/tools.rb +++ b/lib/morfo/tools.rb @@ -1,5 +1,22 @@ module Morfo module Tools + module ExtendedHash + extend self + + def symbolize_keys(hash) + transform_keys(hash) { |key| key.to_sym rescue key } + end + + def transform_keys(hash) + return hash.enum_for(:transform_keys) { size } unless block_given? + result = {} + hash.each_key do |key| + result[yield(key)] = hash[key] + end + result + end + end + class FlattenHashKeys attr_reader :input_hash @@ -10,10 +27,10 @@ def initialize(input_hash) def flatten input_hash.inject({}) do |result_hash, (key, value)| inner_hash = false - if value.is_a?(Hash) + if value.is_a?(::Hash) inner_hash = true value.each do |inner_key, inner_value| - if inner_value.is_a?(Hash) + if inner_value.is_a?(::Hash) inner_hash = true end result_hash.merge!("#{key}.#{inner_key}".to_sym => inner_value) diff --git a/morfo.gemspec b/morfo.gemspec index 53a83c3..c34a9b2 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.0.0' spec.add_runtime_dependency "json" - spec.add_runtime_dependency "activesupport", "< 5.0" + spec.add_runtime_dependency "deep_merge" spec.add_runtime_dependency "rubysl" if RUBY_ENGINE == "rbx" spec.add_development_dependency "rake" From 3b9470acf8a60981213100bcec5269828be231e0 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Fri, 31 Mar 2017 16:31:01 +0200 Subject: [PATCH 86/90] bump version --- lib/morfo/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morfo/version.rb b/lib/morfo/version.rb index 8fe2ab1..0b43b1f 100644 --- a/lib/morfo/version.rb +++ b/lib/morfo/version.rb @@ -1,3 +1,3 @@ module Morfo - VERSION = "0.6.0" + VERSION = "0.7.0" end From 5ff721d8ce4293fc9b45fa8ab8b3fc781394331c Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 3 Aug 2020 12:46:24 -0700 Subject: [PATCH 87/90] Add .circleci/config.yml --- .circleci/config.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..1806bbf --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,35 @@ +version: 2.0 + +shared: &shared + working_directory: ~/morfo + steps: + - checkout + - run: bundle install --path vendor/bundle + - run: + name: Run tests + command: bundle exec rspec + +jobs: + "ruby-2.5": + <<: *shared + docker: + - image: circleci/ruby:2.5 + + "ruby-2.6": + <<: *shared + docker: + - image: circleci/ruby:2.6 + + + "ruby-2.7": + <<: *shared + docker: + - image: circleci/ruby:2.7 + +workflows: + version: 2 + build: + jobs: + - "ruby-2.5" + - "ruby-2.6" + - "ruby-2.7" \ No newline at end of file From b4a139ca82c8cb2bfe13fe12223a250e5c95e3fd Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 3 Aug 2020 12:49:18 -0700 Subject: [PATCH 88/90] remove travis --- .gitignore | 1 + .travis.yml | 24 ------------------------ 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 .travis.yml diff --git a/.gitignore b/.gitignore index d87d4be..a28fccc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ spec/reports test/tmp test/version_tmp tmp +vendor/bundle diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3bf09bb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -sudo: false -language: ruby -cache: bundler -bundler_args: --without development -rvm: - - 2.0.0 - - 2.1.8 - - 2.2.4 - - 2.3.0 - - 2.4.0 - - jruby-9.0.5.0 - - ruby-head - - jruby-head - - rbx-2.11 - - rbx-3.14 -matrix: - fast_finish: true - allow_failures: - - rvm: ruby-head - - rvm: jruby-head - - rvm: rbx-2.11 - - rvm: rbx-3.14 -script: - - bundle exec rspec From 24f6be700ecbee61d5c561dac0502104608ea4c6 Mon Sep 17 00:00:00 2001 From: Leif Gensert Date: Mon, 3 Aug 2020 12:49:59 -0700 Subject: [PATCH 89/90] remove bundler requirement --- morfo.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/morfo.gemspec b/morfo.gemspec index c34a9b2..cffb72b 100644 --- a/morfo.gemspec +++ b/morfo.gemspec @@ -24,5 +24,4 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "rubysl" if RUBY_ENGINE == "rbx" spec.add_development_dependency "rake" - spec.add_development_dependency "bundler", "~> 1.3" end From c742a4c6dcda59dc5ce0cea221fc438a06a55d99 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 19:11:28 +0000 Subject: [PATCH 90/90] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..452ebb3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10