parsable_hash 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.9.2
6
+ - 1.8.7
7
+ - jruby-18mode
8
+ - jruby-19mode
9
+ - rbx-2.1.1
10
+ - ree
11
+
12
+ before_install:
13
+ - gem update --system 2.1.11
14
+ - gem --version
15
+
16
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in parsable_hash.gemspec
4
+ gemspec
5
+
6
+ gem 'pry', :require => 'pry'
7
+ gem 'pry-nav', :require => 'pry-nav'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Konrad Oleksiuk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,114 @@
1
+ [![Code Climate](https://codeclimate.com/github/koleksiuk/parsable_hash.png)](https://codeclimate.com/github/koleksiuk/parsable_hash)
2
+ [![Build Status](https://secure.travis-ci.org/koleksiuk/parsable_hash.png)](http://travis-ci.org/koleksiuk/parsable_hash)
3
+
4
+ # ParsableHash
5
+
6
+ Allows to parse hash values in easy way, by extending class with hash
7
+ strategies.
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'parsable_hash'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install parsable_hash
21
+
22
+ ## Usage
23
+
24
+ class FooTx
25
+ include ParsableHash
26
+
27
+ parse_strategy :transaction_strategy, :date => :date,
28
+ :amount => :big_decimal,
29
+ :id => :integer
30
+
31
+ attr_accessor :transaction
32
+
33
+ def initialize(raw_transaction = {})
34
+ self.transaction = parse_hash(raw_transaction, with: :transaction_strategy)
35
+ end
36
+ end
37
+
38
+ # Fetch transaction from e.g. queue
39
+ transaction = { :date => '12-12-2013', :amount => '23.22', :id => '124564' }
40
+
41
+ tx = FooTx.new(transaction)
42
+
43
+ tx.transaction #=> { :date=>#<Date: 2013-12-12 ((2456639j,0s,0n),+0s,2299161j)>, :amount=>23.22, :id=>124564 }
44
+
45
+ ## Available parsers
46
+ :big_decimal => BigDecimal
47
+ :boolean => Boolean
48
+ :date => Date
49
+ :float => Float
50
+ :integer => Integer
51
+ :date_time => DateTime
52
+ :null => default parser (if parser is missing it simply leaves value as it is)
53
+
54
+ ### Parsing with modules and other features
55
+
56
+ # Using strategies from other modules / classes
57
+ module ParseStrategies
58
+ include ParsableHash
59
+
60
+ parse_strategy :transaction_strategy, :date => :date,
61
+ :amount => :big_decimal,
62
+ :id => :integer
63
+ end
64
+
65
+ class FooTx
66
+ include ParsableHash
67
+
68
+
69
+ attr_accessor :transaction
70
+
71
+ def initialize(raw_transaction = {})
72
+ self.transaction = parse_hash(
73
+ raw_transaction,
74
+ :with => :transaction_strategy,
75
+ :const => ParseStrategies # must be kind of module/class
76
+ )
77
+ end
78
+ end
79
+
80
+
81
+ ## Extending with own parsers
82
+
83
+ module ParsableHash
84
+ module Converters
85
+ class MyClass < Base
86
+ private
87
+
88
+ def try_convert
89
+ ::MyClass.parse_string(@value)
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ And then use ```:my_class``` as parse strategy for key
96
+
97
+ ## Configuration
98
+ ParsableHash::Strategy.fallbacks = true # set to false if you want to raise ParsableHash::MissingStrategy if strategy doesn't exist.
99
+ # In other case it is empty strategy (you can override it by adding :default to your strategies)
100
+
101
+ ## To-do
102
+ * Add support for adding default values
103
+ * Add support for configurable fallback to string if key as symbol is missing (&
104
+ vice-versa)
105
+
106
+
107
+
108
+ ## Contributing
109
+
110
+ 1. Fork it
111
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
112
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
113
+ 4. Push to the branch (`git push origin my-new-feature`)
114
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,49 @@
1
+ require 'parsable_hash/version'
2
+ require 'parsable_hash/strategies'
3
+ require 'parsable_hash/converters/base'
4
+ require 'parsable_hash/strategy/converter_loader'
5
+ require 'parsable_hash/hash_strategy'
6
+ require 'parsable_hash/parser'
7
+
8
+ require 'parsable_hash/core_ext/hash/parse_with'
9
+
10
+ module ParsableHash
11
+ NotDefinedError = Class.new(StandardError)
12
+
13
+ def self.included(klass)
14
+ klass.send(:include, InstanceMethods)
15
+ klass.extend(ClassMethods)
16
+ end
17
+
18
+ module InstanceMethods
19
+ def parse_hash(object, options = {})
20
+ parse_with_strategy(object, options[:with], options[:const])
21
+ end
22
+
23
+ private
24
+
25
+ def parse_with_strategy(hash, strategy, const = nil)
26
+ if const && const.is_a?(Module)
27
+ raise NotDefinedError.new unless const.include?(ParsableHash)
28
+
29
+ Parser.new(hash, const.parse_strategies[strategy]).call
30
+ else
31
+ Parser.new(hash, parse_strategies[strategy]).call
32
+ end
33
+ end
34
+
35
+ def parse_strategies
36
+ self.class.parse_strategies
37
+ end
38
+ end
39
+
40
+ module ClassMethods
41
+ def parse_strategy(name, options = {})
42
+ parse_strategies.add(name, options)
43
+ end
44
+
45
+ def parse_strategies
46
+ @strategies ||= Strategies.new
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ module ParsableHash
2
+ module Converters
3
+ class Base
4
+ def initialize(value)
5
+ @value = value
6
+ end
7
+
8
+ def call(&block)
9
+ try_convert
10
+ rescue => e
11
+ if block_given?
12
+ block.call(@value)
13
+ else
14
+ @value
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def try_convert
21
+ raise NotImplementedError.new
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ Gem.find_files("parsable_hash/converters/*.rb").delete_if {|f| f =~ /base/ }.each {|f| require f }
@@ -0,0 +1,13 @@
1
+ require 'bigdecimal'
2
+
3
+ module ParsableHash
4
+ module Converters
5
+ class BigDecimal < Base
6
+ private
7
+
8
+ def try_convert
9
+ ::BigDecimal.new(@value)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module ParsableHash
2
+ module Converters
3
+ class Boolean < Base
4
+ private
5
+
6
+ def try_convert
7
+ @value.to_s == "true"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'date' if RUBY_VERSION < '1.9.3'
2
+
3
+ module ParsableHash
4
+ module Converters
5
+ class Date < Base
6
+ private
7
+
8
+ def try_convert
9
+ ::Date.parse(@value)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module ParsableHash
2
+ module Converters
3
+ class Float < Base
4
+ private
5
+
6
+ def try_convert
7
+ @value.to_f
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module ParsableHash
2
+ module Converters
3
+ class Integer < Base
4
+ private
5
+
6
+ def try_convert
7
+ @value.to_i
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module ParsableHash
2
+ module Converters
3
+ class Null < Base
4
+ private
5
+
6
+ def try_convert
7
+ @value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ Hash.class_eval do
2
+ define_method :parse_with do |object, strategy|
3
+ if object.is_a? Module
4
+ raise ParsableHash::NotDefinedError.new unless object.include?(ParsableHash)
5
+
6
+ ParsableHash::Parser.new(self, object.parse_strategies[strategy]).call
7
+ else
8
+ object.parse_hash(self, :with => strategy)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ module ParsableHash
2
+ class HashStrategy
3
+ include Enumerable
4
+
5
+ attr_reader :hash
6
+ attr_accessor :origin_hash
7
+
8
+ def initialize(strategy, origin_hash = nil)
9
+ @hash = prepare(strategy)
10
+ self.origin_hash = origin_hash
11
+
12
+ clean_for_hash!(origin_hash) unless origin_hash.nil?
13
+ end
14
+
15
+ def each(&block)
16
+ hash.each { |strategy| yield strategy }
17
+ end
18
+
19
+ def clean_for_hash!(value_hash)
20
+ @hash = clean_pairs(@hash, value_hash)
21
+ end
22
+
23
+ private
24
+
25
+ def prepare(hash)
26
+ hash.inject({}) do |h, (k, v)|
27
+ h[k] = if v.is_a? Hash
28
+ prepare(v)
29
+ else
30
+ Strategy::ConverterLoader.from_value(v)
31
+ end
32
+
33
+ h
34
+ end
35
+ end
36
+
37
+ def clean_pairs(strategy_hash, value_hash)
38
+ strategy_hash.each_pair do |key, val|
39
+ if value_hash[key].is_a?(Hash)
40
+ if val.is_a?(Hash)
41
+ clean_pairs(strategy_hash[key], value_hash[key])
42
+ else
43
+ strategy_hash.delete(key)
44
+ end
45
+ else
46
+ strategy_hash.delete(key) unless value_hash.has_key?(key)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ module ParsableHash
2
+ class Parser
3
+ def initialize(hash, strategy)
4
+ @hash = hash
5
+ @strategy = HashStrategy.new(strategy, @hash)
6
+ end
7
+
8
+ def call
9
+ deep_parse(@hash, @strategy.hash)
10
+ end
11
+
12
+ private
13
+ def deep_parse(hash, strategy)
14
+ hash.merge(strategy) do |key, val, str|
15
+ if val.is_a?(Hash) && str.is_a?(Hash)
16
+ deep_parse(val, str)
17
+ else
18
+ parse_with_strategy(val, str)
19
+ end
20
+ end
21
+ end
22
+
23
+ def parse_with_strategy(value, strategy)
24
+ strategy.call(value)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ module ParsableHash
2
+ MissingStrategy = Class.new(StandardError)
3
+
4
+ class Strategies
5
+ class << self
6
+ def fallbacks
7
+ @fallbacks.nil? ? true : @fallbacks
8
+ end
9
+
10
+ attr_writer :fallbacks
11
+ end
12
+
13
+ def initialize(value = {})
14
+ strategies[:default] = value
15
+ end
16
+
17
+ def [](strategy)
18
+ get(strategy)
19
+ end
20
+
21
+ def []=(strategy, value)
22
+ add(strategy, value)
23
+ end
24
+
25
+ def get(strategy)
26
+ _strategy = strategies[strategy.to_sym]
27
+
28
+ return _strategy unless _strategy.nil?
29
+ return strategies[:default] if _strategy.nil? && self.class.fallbacks
30
+
31
+ raise MissingStrategy.new("Strategy #{strategy} is missing!")
32
+ end
33
+
34
+ def add(strategy, value)
35
+ strategies[strategy] = value
36
+ end
37
+
38
+ private
39
+
40
+ def strategies
41
+ @strategies ||= {}
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ module ParsableHash
2
+ module Strategy
3
+ class ConverterLoader
4
+ def self.from_value(value)
5
+ if value.is_a? Class
6
+ from_class(value)
7
+ else
8
+ from_name(value)
9
+ end
10
+ end
11
+
12
+ def self.from_name(name)
13
+ new(name)
14
+ end
15
+
16
+ def self.from_class(klass)
17
+ new(nil, :converter => klass)
18
+ end
19
+
20
+ attr_accessor :klass
21
+
22
+ def initialize(name, options = {})
23
+ @name = name.to_s
24
+ self.klass = options.fetch(:converter) { load_from_name }
25
+ end
26
+
27
+ def call(val)
28
+ klass.new(val).call
29
+ end
30
+
31
+ private
32
+
33
+ def load_from_name
34
+ klass = camelize(@name)
35
+
36
+ if ParsableHash::Converters.const_defined?(klass)
37
+ ParsableHash::Converters.const_get(klass)
38
+ else
39
+ ParsableHash::Converters::Null
40
+ end
41
+ end
42
+
43
+ def camelize(name)
44
+ name.split('_').map(&:capitalize).join
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module ParsableHash
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'parsable_hash/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "parsable_hash"
8
+ spec.version = ParsableHash::VERSION
9
+ spec.authors = ["Konrad Oleksiuk"]
10
+ spec.email = ["konole@gmail.com"]
11
+ spec.description = %q{Allows to parse hash with e.g. string values to other class instances}
12
+ spec.summary = %q{ParsableHash extension for classes}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Converters::BigDecimal do
4
+ describe '#call' do
5
+ context 'when value can be parsed' do
6
+ subject { described_class.new('5.3') }
7
+
8
+ it 'should return float object' do
9
+ expect(subject.call).to eq(BigDecimal.new('5.3'))
10
+ end
11
+ end
12
+
13
+ it_should_behave_like "with fallback"
14
+ end
15
+ end
16
+
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Converters::Boolean do
4
+ describe '#call' do
5
+ describe 'when value == "true"' do
6
+ subject { described_class.new('true') }
7
+
8
+ it 'should return true value' do
9
+ expect(subject.call).to eq(true)
10
+ end
11
+ end
12
+
13
+ describe 'when value != "true"' do
14
+ subject { described_class.new('falsy') }
15
+
16
+ it 'should return false value' do
17
+ expect(subject.call).to eq(false)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Converters::Date do
4
+ describe '#call' do
5
+ context 'when value can be parsed' do
6
+ let(:date_str) { '12.12.2012' }
7
+ let(:date_obj) { Date.parse(date_str) }
8
+
9
+ subject { described_class.new('12.12.2012') }
10
+
11
+ it 'should return date object' do
12
+ expect(subject.call).to eq(date_obj)
13
+ end
14
+ end
15
+
16
+ it_should_behave_like "with fallback"
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Converters::Float do
4
+ describe '#call' do
5
+ context 'when value can be parsed' do
6
+ subject { described_class.new('5.3') }
7
+
8
+ it 'should return float object' do
9
+ expect(subject.call).to eq(5.3)
10
+ end
11
+ end
12
+
13
+ it_should_behave_like "with fallback"
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Converters::Integer do
4
+ describe '#call' do
5
+ context 'when value can be parsed' do
6
+ subject { described_class.new('5') }
7
+
8
+ it 'should return integer object' do
9
+ expect(subject.call).to eq(5)
10
+ end
11
+ end
12
+
13
+ it_should_behave_like "with fallback"
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Converters::Null do
4
+ describe '#call' do
5
+ [3, 'test', Object.new, {}].each do |obj|
6
+ it "should return passed object (e.g. #{obj.class}) without any change" do
7
+ converter = described_class.new(obj)
8
+
9
+ expect(converter.call).to eq(obj)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ module DummyParsers
4
+ include ParsableHash
5
+
6
+ parse_strategy :rands, :int => :integer, :fl => :float, :nested => { :dt => :date }
7
+ end
8
+
9
+ class Dummy
10
+ include ParsableHash
11
+
12
+ parse_strategy :random_values, :int => :integer, :fl => :float, :nested => { :dt => :date }
13
+ parse_strategy :int, :int => :integer
14
+
15
+ def initialize(options = {})
16
+ @options = options
17
+ end
18
+
19
+ def call
20
+ parse_hash(@options, :with => :random_values)
21
+ end
22
+
23
+ def call_with_const
24
+ parse_hash(@options, :with => :rands, :const => DummyParsers)
25
+ end
26
+
27
+ def call_with_wrong_const
28
+ parse_hash(@options, :with => :rands, :const => Object)
29
+ end
30
+
31
+ def direct_call
32
+ @options.parse_with(self, :random_values)
33
+ end
34
+
35
+ def const_call
36
+ @options.parse_with(DummyParsers, :rands)
37
+ end
38
+ end
39
+
40
+ describe Dummy do
41
+ describe '#parse_hash' do
42
+ context 'when all values exist' do
43
+ let(:obj) { Dummy.new(:int => '3', :fl => '3.5', :nested => { :dt => str_date }) }
44
+ let(:str_date) { '12.12.2013' }
45
+ let(:obj_date) { Date.parse(str_date) }
46
+
47
+ after do
48
+ expect(parsed_obj_with_hash).to eq({
49
+ :int => 3,
50
+ :fl => 3.5,
51
+ :nested => { :dt => obj_date },
52
+ })
53
+ end
54
+
55
+ describe 'call' do
56
+ let(:parsed_obj_with_hash) { obj.call }
57
+
58
+ it 'should parse hash to values passed with given strategy' do
59
+ end
60
+ end
61
+
62
+ describe 'call with constant passed' do
63
+ let(:parsed_obj_with_hash) { obj.call_with_const }
64
+
65
+ it 'should parse hash to values passed with given strategy' do
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ describe 'call with wrong constant passed' do
72
+ let(:obj) { Dummy.new }
73
+
74
+ it 'should raise an error' do
75
+ expect {
76
+ obj.call_with_wrong_const
77
+ }.to raise_error(ParsableHash::NotDefinedError)
78
+ end
79
+ end
80
+
81
+ context 'when hash value is missing' do
82
+ let(:obj) { Dummy.new(:int => '3', :nested => { :foo => 'test' }, :bar => { :test => 'foobar' }) }
83
+
84
+ let(:parsed_obj_with_hash) { obj.call }
85
+ it 'should not create new key-value pair in hash' do
86
+ expect(parsed_obj_with_hash).to eq({
87
+ :int => 3,
88
+ :nested => { :foo => 'test' },
89
+ :bar => { :test => 'foobar' },
90
+ })
91
+ end
92
+ end
93
+ end
94
+
95
+ describe 'hash#parse_with' do
96
+ let(:str_date) { '12.12.2013' }
97
+ let(:obj_date) { Date.parse(str_date) }
98
+ let(:obj) { Dummy.new(:int => '3', :fl => '3.5', :nested => { :dt => str_date }) }
99
+
100
+ after do
101
+ expect(subject).to eq({
102
+ :int => 3,
103
+ :fl => 3.5,
104
+ :nested => { :dt => obj_date },
105
+ })
106
+ end
107
+
108
+ context 'with self' do
109
+ subject { obj.direct_call }
110
+ it 'should parse hash to values passed with given strategy' do
111
+ end
112
+ end
113
+
114
+ context 'with class / module' do
115
+ subject { obj.const_call }
116
+ it 'should parse hash to values passed with given strategy' do
117
+ end
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::HashStrategy do
4
+
5
+ let(:integer_klass) { double('integer_klass') }
6
+ let(:float_klass) { double('float_klass') }
7
+ let(:object_klass) { double('object_klass') }
8
+
9
+ context 'when all values are symbols' do
10
+ before do
11
+ ParsableHash::Strategy::ConverterLoader.stub(:from_name).with(:integer) { integer_klass }
12
+ ParsableHash::Strategy::ConverterLoader.stub(:from_name).with(:float) { float_klass }
13
+ end
14
+
15
+ subject { described_class.new(:one => :integer, :two => :float) }
16
+
17
+ it 'should replace them with callable converters' do
18
+ expect(subject.hash).to eq({
19
+ :one => integer_klass,
20
+ :two => float_klass,
21
+ })
22
+ end
23
+ end
24
+
25
+ context 'when there are direct class passed' do
26
+ before do
27
+ ParsableHash::Strategy::ConverterLoader.stub(:from_name).with(:integer) { integer_klass }
28
+ ParsableHash::Strategy::ConverterLoader.stub(:from_class).with(Object) { object_klass }
29
+ end
30
+
31
+ subject { described_class.new(:one => :integer, :two => Object) }
32
+
33
+ it 'should leave them as they are' do
34
+ expect(subject.hash).to eq({
35
+ :one => integer_klass,
36
+ :two => object_klass,
37
+ })
38
+ end
39
+ end
40
+
41
+ describe 'cleaning up strategy hash' do
42
+ it 'should be possible with passing value hash into initializer' do
43
+ pending
44
+ end
45
+
46
+ it 'should be possible by invoking directly #clean_for_hash!' do
47
+ pending
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ require 'pry'
2
+ require './lib/parsable_hash'
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # Require this file using `require "spec_helper"` to ensure that it is only
7
+ # loaded once.
8
+ #
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ Gem.find_files("support/**/*.rb").each {|f| require f }
11
+
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+
17
+ # Run specs in random order to surface order dependencies. If you find an
18
+ # order dependency and want to debug it, you can fix the order by providing
19
+ # the seed, which is printed after each run.
20
+ # --seed 1234
21
+ config.order = 'random'
22
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Strategies do
4
+ let(:strategies) { described_class.new }
5
+ let(:foo_strategy) { { :bar => :integer } }
6
+
7
+ describe 'on initialize' do
8
+ it 'should have at least default strategy' do
9
+ expect(strategies[:default]).to eq({})
10
+ end
11
+ end
12
+
13
+ describe 'accessing strategry' do
14
+
15
+ before { strategies.add(:new, foo_strategy) }
16
+
17
+ it 'should return strategy with array accessor' do
18
+ expect(strategies[:new]).to eq(foo_strategy)
19
+ end
20
+
21
+ describe '#get' do
22
+ context 'for string' do
23
+ it 'should return strategy' do
24
+ expect(strategies.get('new')).to eq(foo_strategy)
25
+ end
26
+ end
27
+
28
+ context 'for symbol' do
29
+ it 'should return strategy' do
30
+ expect(strategies.get(:new)).to eq(foo_strategy)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ describe 'setting strategy' do
37
+ it 'should allow to set strategy with array accessor' do
38
+ strategies[:new] = foo_strategy
39
+ expect(strategies.get(:new)).to eq(foo_strategy)
40
+ end
41
+
42
+ it 'should allow to set strategy with #add method' do
43
+ strategies.add(:new, foo_strategy)
44
+ expect(strategies.get(:new)).to eq(foo_strategy)
45
+ end
46
+ end
47
+
48
+
49
+ describe 'dealing with missing strategies' do
50
+ context 'by default' do
51
+ it 'should return default strategy' do
52
+ expect(strategies[:foobar]).to eq({})
53
+ end
54
+ end
55
+
56
+ context 'when set fallbacks to false' do
57
+ before { ParsableHash::Strategies.fallbacks = false }
58
+ after { ParsableHash::Strategies.fallbacks = true }
59
+
60
+ it 'should raise an error' do
61
+ expect {
62
+ strategies[:foobar]
63
+ }.to raise_error(ParsableHash::MissingStrategy, "Strategy foobar is missing!")
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe ParsableHash::Strategy::ConverterLoader do
4
+ let(:klass) { double('my_converter') }
5
+
6
+ before do
7
+ ParsableHash::Converters.stub(:const_defined?).with('MyConverter') { true }
8
+ ParsableHash::Converters.stub(:const_get).with('MyConverter') { klass }
9
+ end
10
+
11
+ describe 'coverter loading' do
12
+ subject { described_class.new(:my_converter) }
13
+
14
+ it 'should load converter class from name' do
15
+ expect(subject.klass).to eq(klass)
16
+ end
17
+ end
18
+
19
+ describe 'calling converter' do
20
+ before do
21
+ klass.stub(:new).with('my_val').and_return(inst = double('converter_inst', :call => :my_val))
22
+ end
23
+
24
+ it 'should allow to load converter by passed symbol' do
25
+ expect(described_class.new(:my_converter).call('my_val')).to eq(:my_val)
26
+ end
27
+ end
28
+
29
+
30
+ it 'should allow to initialize loader with given converter' do
31
+ expect(described_class.from_class(klass).klass).to eq(klass)
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ shared_examples "with fallback" do
2
+ context 'when value can not be parsed' do
3
+ subject { described_class.new([1,2]) }
4
+
5
+ describe 'and block is given' do
6
+ it 'should return value of called block' do
7
+ expect(subject.call {|x| x.count }).to eq(2)
8
+ end
9
+ end
10
+
11
+ describe 'and block is missing' do
12
+ it 'should return unchanged value' do
13
+ expect(subject.call).to eq([1,2])
14
+ end
15
+ end
16
+ end
17
+ end
data/tags ADDED
@@ -0,0 +1,3 @@
1
+ Copyright LICENSE.txt /^Copyright (c) 2013 Konrad Oleksiuk$/
2
+ parsable_hash Gemfile.lock /^ parsable_hash (0.0.1)$/
3
+ unshift parsable_hash.gemspec /^$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?/
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parsable_hash
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Konrad Oleksiuk
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2014-01-04 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bundler
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 9
30
+ segments:
31
+ - 1
32
+ - 3
33
+ version: "1.3"
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: rspec
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ description: Allows to parse hash with e.g. string values to other class instances
65
+ email:
66
+ - konole@gmail.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - .gitignore
75
+ - .rspec
76
+ - .travis.yml
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - lib/parsable_hash.rb
82
+ - lib/parsable_hash/converters/base.rb
83
+ - lib/parsable_hash/converters/big_decimal.rb
84
+ - lib/parsable_hash/converters/boolean.rb
85
+ - lib/parsable_hash/converters/date.rb
86
+ - lib/parsable_hash/converters/float.rb
87
+ - lib/parsable_hash/converters/integer.rb
88
+ - lib/parsable_hash/converters/null.rb
89
+ - lib/parsable_hash/core_ext/hash/parse_with.rb
90
+ - lib/parsable_hash/hash_strategy.rb
91
+ - lib/parsable_hash/parser.rb
92
+ - lib/parsable_hash/strategies.rb
93
+ - lib/parsable_hash/strategy/converter_loader.rb
94
+ - lib/parsable_hash/version.rb
95
+ - parsable_hash.gemspec
96
+ - spec/converters/big_decimal_spec.rb
97
+ - spec/converters/boolean_spec.rb
98
+ - spec/converters/date_spec.rb
99
+ - spec/converters/float_spec.rb
100
+ - spec/converters/integer_spec.rb
101
+ - spec/converters/null_spec.rb
102
+ - spec/dummy/module_spec.rb
103
+ - spec/hash_strategy_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/strategies_spec.rb
106
+ - spec/strategy/converter_loader_spec.rb
107
+ - spec/support/converters/with_fallback.rb
108
+ - tags
109
+ has_rdoc: true
110
+ homepage: ""
111
+ licenses:
112
+ - MIT
113
+ post_install_message:
114
+ rdoc_options: []
115
+
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ requirements: []
137
+
138
+ rubyforge_project:
139
+ rubygems_version: 1.6.2
140
+ signing_key:
141
+ specification_version: 3
142
+ summary: ParsableHash extension for classes
143
+ test_files:
144
+ - spec/converters/big_decimal_spec.rb
145
+ - spec/converters/boolean_spec.rb
146
+ - spec/converters/date_spec.rb
147
+ - spec/converters/float_spec.rb
148
+ - spec/converters/integer_spec.rb
149
+ - spec/converters/null_spec.rb
150
+ - spec/dummy/module_spec.rb
151
+ - spec/hash_strategy_spec.rb
152
+ - spec/spec_helper.rb
153
+ - spec/strategies_spec.rb
154
+ - spec/strategy/converter_loader_spec.rb
155
+ - spec/support/converters/with_fallback.rb