yahm 0.0.13 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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