yahm 0.0.13 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2268a85f2c3678373c5876d84572eef2c5c42d1d
4
- data.tar.gz: c83ef4d99b06fe6cb44e2adae92c926867279abe
3
+ metadata.gz: 2f91884e21f0dc68a1777f51cda708d53f053608
4
+ data.tar.gz: c3ad59e14872e528f9a19d3b203645dbc9f9d36e
5
5
  SHA512:
6
- metadata.gz: 6468ee781b3d6a486e432478773037c461d2409156c54c78e0f7dd4ee1297b1a3ceec42d6489cfa3b7aa8a2af93f788e9a319fbaac1e4f9d0217d3117baada10
7
- data.tar.gz: 96bf352b68b0731ce51fee14490fb7c1a3898812c9d5ff5e48ee07246b9bfe62a824eb7d8c11557a883a032feadf0709a9f983a39ac9cea3935cfa24a9f94995
6
+ metadata.gz: 9d57cb78535fd2ef77a611088ed6311d0932b406e27b366de9f9ddc7477837ca14c7bbfb3814f3ce7f36a430e808186fb088528addbc979e151bcd6e0c0933ae
7
+ data.tar.gz: a59e91fd750bd9640cee0e3d71bb989fa7a6b8fafdf0748f927f725373344ff7e03649b18e92d5133edac055992c379bc314d592b890d8b8dddaba9cc1afce54
data/CHANGELOG.md ADDED
@@ -0,0 +1,85 @@
1
+ ## 0.0.x => 0.1.x
2
+
3
+ ### `Yahm::Mapping`
4
+
5
+ #### Constructor now takes a block directly
6
+ ```ruby
7
+
8
+ require "yahm"
9
+
10
+ mapping = Yahm::Mapping.new do
11
+ map "/foo", to: "/bar"
12
+ end
13
+ ```
14
+
15
+ #### Constructor takes a new option ```:context```, which is used for lambda/proc method resolution
16
+ ```ruby
17
+ some_class = Class.new do
18
+ require "yahm"
19
+
20
+ def some_method(value)
21
+ value.to_s + "bar"
22
+ end
23
+
24
+ def mapping
25
+ @mapping ||= Yahm::Mapping.new(context: self) do
26
+ map "/foo", to: "/bar", processed_by: lambda { |v| some_method(v) }
27
+ end
28
+ end
29
+
30
+ def apply_mapping_to(hash)
31
+ mapping.apply_to(hash)
32
+ end
33
+ end
34
+
35
+ some_class.new.apply_mapping_to({foo: "foo"})) # => {:bar=>"foobar"}
36
+ ```
37
+
38
+ #### Renamed `translate_hash` to `apply_to`
39
+
40
+ ```ruby
41
+
42
+ require "yahm"
43
+
44
+ mapping = Yahm::Mapping.new do
45
+ map "/foo", to: "/bar"
46
+ end
47
+
48
+ # before
49
+ # mapping.translate_hash({foo:1})
50
+
51
+ # now
52
+ mapping.apply_to({foo:1})
53
+ ```
54
+
55
+ #### Lambdas/Procs as to: option now take two parameters ```|value, result|```
56
+
57
+ ```ruby
58
+ require "yahm"
59
+
60
+ Yahm::Mapping.new do
61
+ map "/source", to: lambda { |v, result| result.merge!(target: true) }
62
+ end
63
+ ```
64
+
65
+ ### Removed `Yahm::HashMapper`
66
+
67
+ The modul was removed, because mappings can be stored and executed with plain ruby class/instance methods without the need for a seperate modul to extend classes with.
68
+
69
+ ```ruby
70
+ require "yahm"
71
+
72
+ class Foo
73
+ def initialize(hash)
74
+ @hash = hash
75
+ end
76
+
77
+ @mapping = Yahm::Mapping.new do
78
+ map "/foo", to: "/bar"
79
+ end
80
+
81
+ def apply_mapping_to(hash = @hash)
82
+ @mapping.apply_to(hash)
83
+ end
84
+ end
85
+ ```
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in yahm.gemspec
4
4
  gemspec
5
+
6
+ gem "pry", "~> 0.9.12.4"
7
+ gem "pry-nav", "~> 0.2.3"
8
+ gem "pry-stack_explorer", "~> 0.4.9.1"
9
+ gem "pry-syntax-hacks", "~> 0.0.6"
data/README.md CHANGED
@@ -18,7 +18,7 @@ Or install it yourself as:
18
18
 
19
19
  ## Dependencies
20
20
 
21
- None
21
+ * thread (https://github.com/meh/ruby-thread) for threading support
22
22
 
23
23
  ## Usage
24
24
 
@@ -28,16 +28,12 @@ None
28
28
  ```ruby
29
29
  require "yahm"
30
30
 
31
- class Record
32
- extend Yahm::HashMapper
33
-
34
- define_mapper :from_other_record do
35
- map "/record/id", to: "/id"
36
- end
31
+ mapping = Yahm::Mapping.new do
32
+ map "/foo", to: "/bar"
37
33
  end
38
34
 
39
- Record.new.from_other_record({ ... })
40
- => { :id => "..." }
35
+ mapping.apply_to({foo: 1})
36
+ # => {:bar=>1}
41
37
  ```
42
38
 
43
39
  ### More advanced examples
@@ -46,90 +42,62 @@ Record.new.from_other_record({ ... })
46
42
  require "yahm"
47
43
 
48
44
  class Record
49
- extend Yahm::HashMapper
50
- attr_accessor :translated_hash
45
+
46
+ def initialize(hash)
47
+ @record = mapping.apply_to(hash)
48
+ end
51
49
 
52
- define_mapper :from_other_record, call_setter: :translated_hash= do
53
- map "/record/id", to: "/id"
54
- map "/record/count", to: "/count", processed_by: :to_i
55
- map "/record/subject[0]", to: "/subjects/most_important_subject"
56
- map "/record/languages", to: "/languages", force_array: true
57
- map "/record/authors", to: "/authors", split_by: ";"
58
- map "/record/version", to: "/version", default: 1
50
+ private
51
+
52
+ def lookup_id(id)
53
+ # ...
54
+ end
55
+
56
+ def mapping
57
+ @mapping ||= Yahm::Mapping.new(context: self) do
58
+ map "/record/id", to: "/id", processed_by: lambda { |id| lookup_id(id) }
59
+ map "/record/count", to: "/count", processed_by: proc { |count| count.to_i }
60
+ map "/record/subject[0]", to: "/subjects/most_important_subject"
61
+ map "/record/languages[]", to: "/languages", force_array: true
62
+ map "/record/authors", to: "/authors", split_by: ";"
63
+ map "/record/version", to: "/version", default: 1
64
+ map "/record/label", to: lambda { |label, result| result[:label] = label }
65
+ end
59
66
  end
60
67
  end
61
68
 
62
- Record.new.from_other_record({ ... })
63
- => { :id => "...", :count => ..., :subjects => { :most_important_subject => "..."}, ... }
64
-
65
- Record.translated_hash
69
+ Record.new({ ... })
66
70
  => { :id => "...", :count => ..., :subjects => { :most_important_subject => "..."}, ... }
67
71
  ```
72
+ ### Options
68
73
 
69
- #### Examples for using `to`
74
+ #### `to: [string|lambda|proc]`
70
75
 
71
- The `to` option of a mapping rule can either be a string, naming the path in the resultung hash or a proc, which is executed in the context of the created mapper. The result hash can be accessed by `@result`.
76
+ The `to` option of a mapping rule can either be a string, naming the path in the resultung hash or a lambda/proc, which is executed within the created mapper. When a `context` option was supplied to `#new` or `apply_to` methods are also resolved against this scope.
72
77
 
73
- ```ruby
74
- require "yahm"
78
+ #### `default: [value]`
75
79
 
76
- class Record
77
- extend Yahm::HashMapper
78
-
79
- define_mapper :from_other_record do
80
- def funky_method(value)
81
- @result[:is_funky] = true if funky?(value)
82
- end
83
-
84
- map "/record/count", to: "count"
85
- map "/record/name", to: lambda { |v| funky_method(v) }
86
- end
87
- end
88
- ```
80
+ If a value would be `nil` and `default` is given, the `default` value is used instead of `nil`.
89
81
 
90
- #### Examples for using `processed_by`
82
+ #### `force_array: [true|false]`
91
83
 
92
- You can pass symbols, lambdas or procs as `processed_by` option. Inside of lambdas or procs, `self` corresponds to the context of the defined mapper. To access the context of to extended object, use `_self`.
84
+ This ensures, that the value written in the result ist always a hash. Even when the result would be `nil`, an empty array is created.
93
85
 
86
+ #### `processed_by: [lambda|proc]`
94
87
 
95
- ```ruby
96
- require "yahm"
88
+ You can pass a lambda/proc as `processed_by` option. Inside of lambdas or procs, `self` corresponds to the context of the defined mapper. When a `context` option was supplied to `#new` or `apply_to` methods are also resolved against this scope.
97
89
 
98
- class Record
99
- extend Yahm::HashMapper
100
-
101
- def process_bool_string(string)
102
- string == "true" ? true : (string == "false" ? false : nil)
103
- end
104
-
105
- define_mapper :from_other_record do
106
- def cheap?(price)
107
- price.to_i < 20
108
- end
109
-
110
- map "/record/id", to: "id", processed_by: :to_i
111
- map "/record/count", to: "count", processed_by: proc { |v| v.to_i }
112
- map "/record/subject", to: "subjects", processed_by: :downcase
113
- map "/record/price", to: "is_cheap", processed_by: lambda { |v| cheap?(v) }
114
- map "/record/is_available", to: "available", processed_by: lambda { |v| _self.process_bool_string(v) }
115
- end
116
- end
117
- ```
90
+ #### `split_by: [string]`
118
91
 
119
- #### Private mappers
92
+ If a value is a string and `split_by` is supplied, the string is splitted by the given character and an array would be returned.
120
93
 
121
- You can control, wether a mapper is defined as a private method or not by passing the `private:[true|false]` option to `define_mapper`.
94
+ #### `use_threads: [true|false]` [experimental]
122
95
 
123
- ```ruby
96
+ Use a thread pool to execute to rules of a mapping. The current implementation is very naiv. No locking of shared data etc. Use this *only* if *you know* what you are doing and if your rules a complex enough to compensate the overhead introduced by threading.
124
97
 
125
- class Record
126
- extend Yahm::HashMapper
127
-
128
- define_mapper :my_private_mapper, private: true do
129
- map "/some/internals", to: "internals"
130
- end
131
- end
132
- ```
98
+ #### `thread_pool_size: [number]` [experimental]
99
+
100
+ The number of threads used to execute rules if `use_threads: true`.
133
101
 
134
102
  ## Related work
135
103
 
data/lib/yahm.rb CHANGED
@@ -1,6 +1,137 @@
1
1
  require "yahm/version"
2
2
 
3
3
  class Yahm
4
- require "yahm/hash_mapper"
5
4
  require "yahm/mapping"
5
+
6
+ def self.get(object, path)
7
+ current_path_element, remaining_path = split_path(path)
8
+
9
+ if hash_path?(current_path_element) && object.is_a?(Hash)
10
+ key = current_path_element[1..-1]
11
+
12
+ # handle cases where one forces symbol/string keys
13
+ value =
14
+ if key.start_with?(":")
15
+ object[key[1..-1].to_sym]
16
+ elsif key.start_with?('"') && key.end_with?('"')
17
+ object[key[1..-2]]
18
+ else
19
+ object[key.to_sym] || object[key.to_s]
20
+ end
21
+
22
+ unless value.nil?
23
+ remaining_path ? self.get(value, remaining_path) : value
24
+ else
25
+ value
26
+ end
27
+ elsif array_path?(current_path_element) && object.is_a?(Array)
28
+ index_parameter = current_path_element.slice(1..-2)
29
+
30
+ index =
31
+ if index_parameter == ""
32
+ 0..object.length
33
+ else
34
+ index_parameter.to_i
35
+ end
36
+
37
+ if remaining_path
38
+ unless object[index].nil?
39
+ map_result = object[index.is_a?(Range) ? index : index..index].map do |array_element|
40
+ self.get(array_element, remaining_path)
41
+ end.compact
42
+
43
+ map_result == [] ? nil : map_result.flatten(array_depth(map_result) - 1)
44
+ else
45
+ nil
46
+ end
47
+ else
48
+ object[index]
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.set(object, path, value)
54
+ current_path_element, remaining_path = split_path(path)
55
+ next_path_element = split_path(remaining_path)[0]
56
+
57
+ if hash_path?(current_path_element) && object.is_a?(Hash)
58
+ key = current_path_element[1..-1]
59
+
60
+ # handle cases where one forces symbol/string keys
61
+ key =
62
+ if key.start_with?(":")
63
+ key[1..-1].to_sym
64
+ elsif key.start_with?('"') && key.end_with?('"')
65
+ key[1..-2]
66
+ else
67
+ key.to_sym # keys should be symbols per default
68
+ end
69
+
70
+ if next_path_element.nil?
71
+ object[key] = value
72
+ elsif hash_path?(next_path_element)
73
+ self.set(object[key] || object[key] = {}, remaining_path, value)
74
+ elsif array_path?(next_path_element)
75
+ self.set(object[key] || object[key] = [], remaining_path, value)
76
+ end
77
+ elsif array_path?(current_path_element) && object.is_a?(Array)
78
+ if hash_path?(next_path_element)
79
+ object.push(_object = {})
80
+ self.set(_object, remaining_path, value)
81
+ end
82
+ end
83
+ end
84
+
85
+ #
86
+ private
87
+ #
88
+ def self.array_depth(array)
89
+ depth = 1
90
+ until array == (flattend_array = array.flatten(1))
91
+ depth += 1
92
+ array = flattend_array
93
+ end
94
+
95
+ depth
96
+ end
97
+
98
+ def self.array_path?(path)
99
+ path[0] == "["
100
+ end
101
+
102
+ def decode_string_or_symbol_from(string)
103
+ if string.start_with?(":")
104
+ string[1..-1].to_sym
105
+ elsif string.start_with?('"') && string.end_with?('"')
106
+ string[1..-2]
107
+ else
108
+ string.to_sym # defaults to symbols
109
+ end
110
+ end
111
+
112
+ def self.hash_path?(path)
113
+ path[0] == "/"
114
+ end
115
+
116
+ def self.split_path(path)
117
+ unless path.nil?
118
+ path_elements = path
119
+ .gsub(/(\/)|(\[)/,
120
+ "/" => " /",
121
+ "[" => " [",
122
+ )
123
+ .split(" ")
124
+
125
+ remaining_path =
126
+ if path_elements.length > 1
127
+ path_elements.slice(1..-1).join
128
+ else
129
+ nil
130
+ end
131
+
132
+ [path_elements.first, remaining_path]
133
+ else
134
+ [nil, nil]
135
+ end
136
+ end
6
137
  end
data/lib/yahm/mapping.rb CHANGED
@@ -1,76 +1,75 @@
1
+ require "thread/pool"
2
+
1
3
  class Yahm::Mapping
2
4
 
3
- attr_reader :_self
5
+ attr_accessor :thread_pool_size
6
+ attr_accessor :use_threads
4
7
 
5
8
  def initialize(options = {}, &block)
9
+ @context = options[:context]
6
10
  @rules = []
7
11
  @result = nil
8
- self.instance_eval(&block) if block
9
- end
12
+ @use_threads = options[:use_threads] || false
13
+ @thread_pool_size = options[:thread_pool_size] || 2
10
14
 
11
- def map(str, options = {})
12
- @rules.push [str, options]
13
- self
15
+ self.instance_eval(&block) if block
14
16
  end
15
17
 
16
- def translate_hash(input_hash, options = {})
17
- @_self = options[:_self]
18
+ def apply_to(input_hash, options = {})
18
19
  @result = {}
19
20
 
20
- @rules.each do |rule|
21
- process_rule(input_hash, rule)
21
+ if @use_threads
22
+ thread_pool = Thread.pool(@thread_pool_size) if @use_threads
23
+
24
+ @rules.each do |rule|
25
+ thread_pool.process do
26
+ apply_rule(rule, input_hash, @result)
27
+ end
28
+ end
29
+
30
+ thread_pool.shutdown
31
+ else
32
+ @rules.each do |rule|
33
+ apply_rule(rule, input_hash, @result)
34
+ end
22
35
  end
23
36
 
24
37
  @result
25
38
  end
26
39
 
27
- alias_method :apply_to, :translate_hash # some syntactic sugar
28
-
29
- private
30
-
31
- def process_rule(input_hash, rule)
32
- sanitized_source_path = rule.first
33
- .sub(/^\//, "")
34
- .gsub(/\[(\d+)\]/, "/\\1")
35
- .split("/")
36
- .map do |element|
37
- if element[/\A\d+\Z/]
38
- element.to_i
39
- else
40
- element.to_sym
41
- end
42
- end
43
-
44
- if (to_option = rule.last[:to]).is_a?(String)
45
- sanitized_target_path = rule.last[:to].sub(/^\//, "").split("/").map(&:to_sym)
46
- target_parent_path = sanitized_target_path.slice(0, sanitized_target_path.length - 1)
40
+ #
41
+ # dynamic context method resolutuon
42
+ #
43
+ def method_missing(sym, *args, &block)
44
+ if @context.respond_to?(sym)
45
+ @context.send(sym, *args, &block)
46
+ else
47
+ super
47
48
  end
49
+ end
48
50
 
49
- source_value = sanitized_source_path.inject(input_hash) { |hash, key| hash.nil? ? nil : hash[key]}
51
+ def respond_to_missing?(sym, *)
52
+ @context.respond_to?(sym) || super
53
+ end
50
54
 
51
- unless (symbol_or_callable = rule.last[:processed_by]).nil?
52
- source_value = if symbol_or_callable.is_a?(Symbol)
53
- source_value.send(symbol_or_callable)
54
- else
55
- symbol_or_callable.call(source_value)
56
- end
57
- end
55
+ private
58
56
 
59
- unless (split_by_character = rule.last[:split_by]).nil?
60
- source_value = source_value.split(split_by_character).map(&:strip) unless source_value.nil? || source_value.is_a?(Array)
61
- end
57
+ def map(path, options = {})
58
+ @rules.push({ path: path }.merge(options))
59
+ self
60
+ end
62
61
 
63
- unless rule.last[:force_array].nil?
64
- source_value = (source_value.is_a?(Array) ? source_value : [source_value]).compact
65
- end
62
+ def apply_rule(rule, input_hash, output_hash)
63
+ value = Yahm.get(input_hash, rule[:path])
64
+ value = rule[:default] && value.nil? ? rule[:default] : value
65
+ value = rule[:force_array] ? (value.nil? ? [] : [value].flatten(1)) : value
66
+ value = rule[:split_by] && value.is_a?(String) ? value.split(rule[:split_by]).map!(&:strip) : value
67
+ value = rule[:processed_by] ? rule[:processed_by].call(value) : value
66
68
 
67
- if to_option.is_a?(String)
68
- target_hash_parent_element = target_parent_path.inject(@result) { |hash, key| hash[key.to_sym] ||= {} }
69
- target_hash_parent_element.merge!({
70
- sanitized_target_path.last => ((source_value.nil? && !(default_value = rule.last[:default]).nil?) ? default_value : source_value)
71
- })
72
- elsif to_option.is_a?(Proc)
73
- instance_exec(source_value, &to_option)
69
+ if rule[:to].is_a?(Proc)
70
+ instance_exec(value, output_hash, &rule[:to])
71
+ else
72
+ Yahm.set(output_hash, rule[:to], value)
74
73
  end
75
74
  end
76
75
  end
data/lib/yahm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Yahm
2
- VERSION = "0.0.13"
2
+ VERSION = "0.1.0"
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require "yahm"
2
- require "pry"
2
+ begin
3
+ require "pry"
4
+ rescue LoadError
5
+ end
3
6
 
4
7
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
5
8
  RSpec.configure do |config|
@@ -1,32 +1,54 @@
1
1
  require_relative "../spec_helper"
2
2
 
3
3
  describe Yahm::Mapping do
4
- it "is a class used internally by Yahm::HashMapper#define_mapper" do
5
- end
4
+ describe "#new" do
5
+ it "can be called with a block, containing the mapping described by the DSL" do
6
+ mapping = Yahm::Mapping.new do
7
+ map "/foo", to: "/bar"
8
+ end
9
+
10
+ expect(mapping.apply_to({foo:1})).to eq({:bar=>1})
11
+ end
12
+
13
+ context "when called with a \"context\" option" do
14
+ it "passes methods from withing the mapping (e.g. procs/lambdas) to the context" do
15
+ some_class = Class.new do
16
+ require "yahm"
6
17
 
7
- describe ".map" do
8
- let(:mapping) { Yahm::Mapping.new }
18
+ def some_method(value)
19
+ value.to_s + "bar"
20
+ end
9
21
 
10
- it "adds a mapping rule to a mapping" do
11
- mapping.map("/foo", to: "bar")
12
- expect(mapping.instance_variable_get :@rules).not_to be_nil
22
+ def mapping
23
+ @mapping ||= Yahm::Mapping.new(context: self) do
24
+ map "/foo", to: "/bar", processed_by: lambda { |v| some_method(v) }
25
+ end
26
+ end
27
+
28
+ def apply_mapping_to(hash)
29
+ mapping.apply_to(hash)
30
+ end
31
+ end
32
+
33
+ expect(some_class.new.apply_mapping_to({foo: "foo"})).to eq({:bar=>"foobar"})
34
+ end
13
35
  end
14
36
  end
15
-
16
- describe ".translate_hash" do
37
+
38
+ describe ".apply_to" do
17
39
  let(:mapping) do
18
- _mapping = Yahm::Mapping.new
19
- _mapping.map "/record_id", to: "/id"
20
- _mapping.map "/record/title", to: "/tile"
21
- _mapping.map "/record/isbns", to: "/my_data/isbns"
22
- _mapping.map "/record/isbns[1]", to: "/main_isbn" # when an array, one can specifiy which element to choose
23
- _mapping.map "/record/count", to: "/count", :processed_by => :to_i # processed_by specifies a method which post_processes to value
24
- _mapping.map "/record/languages", to: "/languages", force_array: true
25
- _mapping.map "/record/authors", to: "/authors", split_by: ";"
26
- _mapping.map "/record/version", to: "/version", default: 1
27
- _mapping.map "/record/creators", to: "/creators", force_array: true # when source value is nil, there should be an empty array in the target hash
28
- _mapping.map "/record/publishers", to: "/publishers", split_by: ";" # if there are is no source value, this should result to nil in the target hash
29
- _mapping
40
+ Yahm::Mapping.new do
41
+ map "/record_id", to: "/id"
42
+ map "/record/title", to: "/title"
43
+ map "/record/isbns", to: "/my_data/isbns"
44
+ map "/record/isbns[1]", to: "/main_isbn" # when an array, one can specifiy which element to choose
45
+ map "/record/count", to: "/count", processed_by: lambda { |v| v.to_i }
46
+ map "/record/languages", to: "/languages", force_array: true
47
+ map "/record/authors", to: "/authors", split_by: ";"
48
+ map "/record/version", to: "/version", default: 1
49
+ map "/record/creators", to: "/creators", force_array: true # when source value is nil, there should be an empty array in the target hash
50
+ map "/record/publishers", to: "/publishers", split_by: ";" # if there are is no source value, this should result to nil in the target hash
51
+ end
30
52
  end
31
53
 
32
54
  let(:input_hash) do
@@ -45,10 +67,10 @@ describe Yahm::Mapping do
45
67
  }
46
68
  end
47
69
 
48
- it "translates a given hash according to @rules" do
49
- expect(mapping.translate_hash(input_hash)).to eq({
70
+ let(:expected_result) do
71
+ {
50
72
  :id=>"some_id123",
51
- :tile=>"some title",
73
+ :title=>"some title",
52
74
  :my_data=>{:isbns=>["3-86680-192-0", "3-680-08783-7"]},
53
75
  :main_isbn=>"3-680-08783-7",
54
76
  :count=>3,
@@ -57,48 +79,57 @@ describe Yahm::Mapping do
57
79
  :version=>1,
58
80
  :creators=>[],
59
81
  :publishers=>nil
60
- })
82
+ }
83
+ end
84
+
85
+ it "translates a given hash according to @rules" do
86
+ expect(mapping.apply_to(input_hash)).to eq(expected_result)
61
87
  end
62
88
 
89
+ context "when threading is enabled" do
90
+ it "translates a given hash according to @rules" do
91
+ mapping.use_threads = true
92
+ expect(mapping.apply_to(input_hash)).to eq(expected_result)
93
+ end
94
+ end
95
+
63
96
  it "does not apply \"split_by\" if source value is an array" do
64
- expect(Yahm::Mapping.new.map("/source/value", to: "/target", split_by: ";").translate_hash(source: { value: ["foo", "foo;bar"] }))
97
+ expect(Yahm::Mapping.new { map "/source/value", to: "/target", split_by: ";" }.apply_to(source: { value: ["foo", "foo;bar"] }))
65
98
  .to eq({
66
99
  :target => ["foo", "foo;bar"]
67
100
  })
68
101
  end
69
102
 
70
103
  it "processes the source value by the lambda/proc given by \"processed_by\"" do
71
- expect(Yahm::Mapping.new.map("/source/value", to: "/target", processed_by: lambda { |v| v.downcase }).translate_hash(source: { value: "A STRING" }))
104
+ expect(Yahm::Mapping.new { map "/source/value", to: "/target", processed_by: lambda { |v| v.downcase } }.apply_to(source: { value: "A STRING" }))
72
105
  .to eq({
73
106
  :target => "a string"
74
107
  })
75
108
 
76
- expect(Yahm::Mapping.new.map("/source/value", to: "/target", processed_by: proc { |v| v.downcase }).translate_hash(source: { value: "A STRING" }))
109
+ expect(Yahm::Mapping.new { map "/source/value", to: "/target", processed_by: proc { |v| v.downcase } }.apply_to(source: { value: "A STRING" }))
77
110
  .to eq({
78
111
  :target => "a string"
79
112
  })
80
113
  end
81
114
 
82
115
  it "returns nil if the source path does not exist" do
83
- expect(Yahm::Mapping.new.map("/source/value/min", to: "/target").translate_hash(foo: "bar"))
84
- .to eq({
116
+ expect(Yahm::Mapping.new { map "/source/value/min", to: "/target" }.apply_to(foo: "bar")).to eq({
85
117
  :target => nil
86
118
  })
87
119
  end
88
120
 
89
121
  it "accepts a lambda/proc as :to option" do
90
- expect(Yahm::Mapping.new.map("/source", to: proc { |v| @result = { target: true } }).translate_hash(foo: "bar"))
122
+ expect(Yahm::Mapping.new { map "/source", to: lambda { |v, result| result.merge!(target: true) } }.apply_to(foo: "bar"))
91
123
  .to eq({
92
124
  :target => true
93
125
  })
94
126
  end
95
127
 
96
128
  it "provides multiple values as an array of values to lambdas/procs" do
97
- expect(Yahm::Mapping.new.map("/source", to: "/array_length", processed_by: lambda { |v| v.length }).translate_hash(source: ["bar", "moep"]))
129
+ expect(Yahm::Mapping.new { map "/source", to: "/array_length", processed_by: lambda { |v| v.length } }.apply_to(source: ["bar", "moep"]))
98
130
  .to eq({
99
131
  :array_length => 2
100
132
  })
101
133
  end
102
134
  end
103
-
104
135
  end
data/spec/yahm_spec.rb CHANGED
@@ -1,8 +1,93 @@
1
1
  require_relative "./spec_helper"
2
2
 
3
3
  describe Yahm do
4
- it "has a module called HashMapper" do
5
- expect(Yahm.const_defined? :HashMapper).to be_true
6
- expect(Yahm::HashMapper.class.is_a? Module)
4
+ it "has a class named Mapping" do
5
+ expect(Yahm.const_defined? :Mapping).to be_true
6
+ expect(Yahm::Mapping.class.is_a? Class)
7
+ end
8
+
9
+ describe "#get" do
10
+ let(:object) do
11
+ {
12
+ hello: "world",
13
+ empty_array: [],
14
+ empty_hash: [],
15
+ "string_key" => "foobar",
16
+ some: {
17
+ really: [
18
+ {
19
+ nested: [
20
+ {
21
+ structure: "hello"
22
+ },
23
+ {
24
+ structure: "world"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ },
30
+ foo: [
31
+ {
32
+ bar: 1
33
+ },
34
+ {
35
+ bar: 2
36
+ }
37
+ ],
38
+ arr_of_arrs: [
39
+ [ [1,2] , [3,4,5] ],
40
+ [ 6,7 ],
41
+ 8
42
+ ]
43
+ }
44
+ end
45
+
46
+ it "returns elements which match the given path" do
47
+ expect(Yahm.get(object, "/hello")).to eq("world")
48
+ expect(Yahm.get(object, "/empty_array")).to eq([])
49
+ expect(Yahm.get(object, "/empty_hash")).to eq([])
50
+ expect(Yahm.get(object, "/foo")).to eq([{:bar=>1}, {:bar=>2}])
51
+ expect(Yahm.get(object, "/foo[0]")).to eq({:bar=>1})
52
+ expect(Yahm.get(object, "/foo[1]")).to eq({:bar=>2})
53
+ expect(Yahm.get(object, "/foo[]/bar")).to eq([1,2])
54
+ expect(Yahm.get(object, "/arr_of_arrs[0][1][2]")).to eq([5])
55
+ expect(Yahm.get(object, "/some/really[]/nested[]/structure")).to eq(["hello", "world"])
56
+ expect(Yahm.get(object, "/arr_of_arrs[0]")).to eq([[1, 2], [3, 4, 5]])
57
+ end
58
+
59
+ it "returns nil if the path is invalid" do
60
+ expect(Yahm.get(object, "/hello_world")).to eq(nil)
61
+ expect(Yahm.get(object, "/foo[3]")).to eq(nil)
62
+ expect(Yahm.get(object, "/foo[]/bar/muff")).to eq(nil)
63
+ expect(Yahm.get(object, "/foo[]/bar[0]")).to eq(nil)
64
+ end
65
+
66
+ it "allows specification of the key type using : or \"\"" do
67
+ expect(Yahm.get(object, '/"string_key"')).to eq("foobar")
68
+ expect(Yahm.get(object, '/:string_key"')).to eq(nil)
69
+ expect(Yahm.get(object, "/string_key")).to eq("foobar")
70
+ end
71
+ end
72
+
73
+ describe "#set" do
74
+ it "sets the objects value according to path and value" do
75
+ Yahm.set(object = {}, "/foo[]/bar", [1,2,3])
76
+ expect(object).to eq({:foo=>[{:bar=>[1, 2, 3]}]})
77
+ end
78
+
79
+ it "allows specification of the key type using : or \"\"" do
80
+ Yahm.set(object = {}, "/:foo/\"bar\"", "foobar")
81
+ expect(object).to eq({:foo=>{"bar"=>"foobar"}})
82
+ end
83
+
84
+ it "extends existing arrays/hashes if present" do
85
+ Yahm.set(object = { foo: [{bar: [1,2,3]}] }, "/foo[]/bar", [4,5,6])
86
+ expect(object).to eq({:foo=>[{:bar=>[1, 2, 3]}, {:bar=>[4, 5, 6]}]})
87
+
88
+ Yahm.set(object = { foo: "bar" }, "/muff", "muffmuff")
89
+ expect(object).to eq({:foo=>"bar", :muff=>"muffmuff"})
90
+ end
91
+
7
92
  end
8
93
  end
data/yahm.gemspec CHANGED
@@ -17,11 +17,9 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
+ spec.add_dependency "thread", ">= 0.1.4"
21
+
20
22
  spec.add_development_dependency "bundler", "~> 1.5"
21
- spec.add_development_dependency "pry", "~> 0.9.12.4"
22
- spec.add_development_dependency "pry-nav", "~> 0.2.3"
23
- spec.add_development_dependency "pry-stack_explorer", "~> 0.4.9.1"
24
- spec.add_development_dependency "pry-syntax-hacks", "~> 0.0.6"
25
- spec.add_development_dependency "rspec", "~> 2.14.1"
23
+ spec.add_development_dependency "rspec", ">= 2.14.1"
26
24
  spec.add_development_dependency "rake"
27
25
  end
metadata CHANGED
@@ -1,97 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yahm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Sievers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-02 00:00:00.000000000 Z
11
+ date: 2014-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.5'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.5'
27
- - !ruby/object:Gem::Dependency
28
- name: pry
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.9.12.4
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.9.12.4
41
- - !ruby/object:Gem::Dependency
42
- name: pry-nav
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.2.3
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.2.3
55
- - !ruby/object:Gem::Dependency
56
- name: pry-stack_explorer
14
+ name: thread
57
15
  requirement: !ruby/object:Gem::Requirement
58
16
  requirements:
59
- - - "~>"
17
+ - - ">="
60
18
  - !ruby/object:Gem::Version
61
- version: 0.4.9.1
62
- type: :development
19
+ version: 0.1.4
20
+ type: :runtime
63
21
  prerelease: false
64
22
  version_requirements: !ruby/object:Gem::Requirement
65
23
  requirements:
66
- - - "~>"
24
+ - - ">="
67
25
  - !ruby/object:Gem::Version
68
- version: 0.4.9.1
26
+ version: 0.1.4
69
27
  - !ruby/object:Gem::Dependency
70
- name: pry-syntax-hacks
28
+ name: bundler
71
29
  requirement: !ruby/object:Gem::Requirement
72
30
  requirements:
73
31
  - - "~>"
74
32
  - !ruby/object:Gem::Version
75
- version: 0.0.6
33
+ version: '1.5'
76
34
  type: :development
77
35
  prerelease: false
78
36
  version_requirements: !ruby/object:Gem::Requirement
79
37
  requirements:
80
38
  - - "~>"
81
39
  - !ruby/object:Gem::Version
82
- version: 0.0.6
40
+ version: '1.5'
83
41
  - !ruby/object:Gem::Dependency
84
42
  name: rspec
85
43
  requirement: !ruby/object:Gem::Requirement
86
44
  requirements:
87
- - - "~>"
45
+ - - ">="
88
46
  - !ruby/object:Gem::Version
89
47
  version: 2.14.1
90
48
  type: :development
91
49
  prerelease: false
92
50
  version_requirements: !ruby/object:Gem::Requirement
93
51
  requirements:
94
- - - "~>"
52
+ - - ">="
95
53
  - !ruby/object:Gem::Version
96
54
  version: 2.14.1
97
55
  - !ruby/object:Gem::Dependency
@@ -117,16 +75,15 @@ extra_rdoc_files: []
117
75
  files:
118
76
  - ".gitignore"
119
77
  - ".rspec"
78
+ - CHANGELOG.md
120
79
  - Gemfile
121
80
  - LICENSE.txt
122
81
  - README.md
123
82
  - Rakefile
124
83
  - lib/yahm.rb
125
- - lib/yahm/hash_mapper.rb
126
84
  - lib/yahm/mapping.rb
127
85
  - lib/yahm/version.rb
128
86
  - spec/spec_helper.rb
129
- - spec/yahm/hash_mapper_spec.rb
130
87
  - spec/yahm/mapping_spec.rb
131
88
  - spec/yahm_spec.rb
132
89
  - yahm.gemspec
@@ -156,6 +113,5 @@ specification_version: 4
156
113
  summary: Yet another ruby hash mapper
157
114
  test_files:
158
115
  - spec/spec_helper.rb
159
- - spec/yahm/hash_mapper_spec.rb
160
116
  - spec/yahm/mapping_spec.rb
161
117
  - spec/yahm_spec.rb
@@ -1,20 +0,0 @@
1
- module Yahm::HashMapper
2
- def define_mapper(mapper_method_name, options = {}, &block)
3
- mapping = Yahm::Mapping.new(&block)
4
-
5
- define_method mapper_method_name do |hash|
6
- translated_hash = mapping.translate_hash(hash, _self: self)
7
- unless (setter_name = options[:call_setter]).nil?
8
- self.send(setter_name, translated_hash)
9
- end
10
-
11
- translated_hash
12
- end
13
-
14
- if options[:private]
15
- private mapper_method_name.to_sym
16
- end
17
-
18
- self
19
- end
20
- end
@@ -1,88 +0,0 @@
1
- require_relative "../spec_helper"
2
-
3
- describe Yahm::HashMapper do
4
- context "when extending a class" do
5
- let(:class_extended_with_hash_mapper) do
6
- class MyClass
7
- extend Yahm::HashMapper
8
- end
9
- end
10
-
11
- it "adds a class method called #define_mapper" do
12
- expect(class_extended_with_hash_mapper.methods).to include(:define_mapper)
13
- end
14
- end
15
-
16
- describe "#define_mapper" do
17
- let(:class_extended_with_hash_mapper) do
18
- Class.new do
19
- extend Yahm::HashMapper
20
-
21
- def method_of_extended_object(value)
22
- true
23
- end
24
-
25
- define_mapper :my_mapper do
26
- def mapper_local_method(value)
27
- true
28
- end
29
-
30
- map "/source[0]", to: "target_1", processed_by: proc { |v| mapper_local_method(v) }
31
- map "/source[1]", to: "target_2", processed_by: proc { |v| _self.method_of_extended_object(v) }
32
- map "/source[2]", to: "target_3", processed_by: proc { |v| another_local_method(v) }
33
-
34
- def another_local_method(value)
35
- true
36
- end
37
- end
38
-
39
- define_mapper :my_private_mapper, private: true do
40
- end
41
- end
42
- end
43
-
44
- it "adds a named mapper method to the instances of the extended class" do
45
- expect(class_extended_with_hash_mapper.new).to respond_to(:my_mapper)
46
- end
47
-
48
- it "provides the context of the defined mapper as \"self\" for \"processed_by\" callables" do
49
- expect(class_extended_with_hash_mapper.new.my_mapper({source: ["foo", "bar"]})).to eq({
50
- target_1: true,
51
- target_2: true,
52
- target_3: true
53
- })
54
- end
55
-
56
- it "provides the context of the extended object as \"_self\" for \"processed_by\" callables" do
57
- expect(class_extended_with_hash_mapper.new.my_mapper({source: ["foo", "bar"]})).to eq({
58
- target_1: true,
59
- target_2: true,
60
- target_3: true
61
- })
62
- end
63
-
64
- context "when called with an option called 'call_setter'" do
65
- let(:class_extended_with_hash_mapper) do
66
- Class.new do
67
- extend Yahm::HashMapper
68
-
69
- attr_accessor :my_result
70
-
71
- define_mapper :my_mapper, call_setter: :my_result= do
72
- end
73
- end
74
- end
75
-
76
- it "calls a method with the given name with the result of the mapping" do
77
- (instance = class_extended_with_hash_mapper.new).my_mapper({})
78
- expect(instance.my_result).not_to be_nil
79
- end
80
- end
81
-
82
- context "when called with private: true" do
83
- it "adds a private named mapper method to the instances of the extended class" do
84
- expect(class_extended_with_hash_mapper.new.private_methods).to include(:my_private_mapper)
85
- end
86
- end
87
- end
88
- end