aduki 0.1.3 → 0.2.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: 79dc256c2fd9daa39c568a3a85d1f30a3104a70b
4
- data.tar.gz: 46d3a666d1711db748a7d8657c7eca5000dd1739
3
+ metadata.gz: b7074743cb0761525eaa92e7b9c6e3c942b2b27d
4
+ data.tar.gz: 757b6d2914b0d256e9b2404f959f85a0bd42662d
5
5
  SHA512:
6
- metadata.gz: 0cf02afb1c9e116f720ade13f302d67fe54d7f1c29195a03211be5d5f7bdddcd804633889cb32da08dc446fced8f4d529aac230138f64308ef41897534daeb62
7
- data.tar.gz: b97ea592dd63f5bb6cd986ccae8ed1f13dfbb03e7885894829df5668aaf078a973d9e3f006af7c1e3eb3dea08ebc932e475306fbeb4ee6464ca68fd2271c9dc7
6
+ metadata.gz: 23d19d50ef586a3dd4a2c29061a2fe8fca8837d235182a97bd9f0dedd1274019761e7b93ca14ef6d9aff0b504fcba8a49d1c6bfa17bb9eadb864b2d209df783a
7
+ data.tar.gz: 32443cdd6d8b1ef2008e1f33b4658f6097d6c17e7c2053937fb6bb47368a5fe4f518809b19913da6e266584750e4270ce47848bb34a3b4469aebdfafb8ca3a86
@@ -1,6 +1,8 @@
1
1
  require "date"
2
2
  require "time"
3
3
  require "aduki/version"
4
+ require "aduki/recursive_hash"
5
+ require "aduki/attr_finder"
4
6
 
5
7
  module Aduki
6
8
  def self.to_aduki obj, collector={ }, key="", join=""
@@ -108,7 +110,7 @@ module Aduki
108
110
  existing_value = object.send setter if object.respond_to?(setter)
109
111
  if existing_value
110
112
  if existing_value.is_a? Hash
111
- existing_value.merge! value
113
+ value.each { |k, v| existing_value[k] = v }
112
114
  else
113
115
  Aduki.apply_attributes existing_value, value
114
116
  end
@@ -169,6 +171,14 @@ module Aduki
169
171
  def get_aduki_initializers
170
172
  @@initializers[self] || []
171
173
  end
174
+
175
+ def attr_finder finder, id, *args
176
+ class_eval Aduki::AttrFinder.attr_finders_text(finder, id, *args)
177
+ end
178
+
179
+ def attr_many_finder finder, id, name, options={ }
180
+ class_eval Aduki::AttrFinder.one2many_attr_finder_text(finder, id, name, options={ })
181
+ end
172
182
  end
173
183
 
174
184
  module Initializer
@@ -0,0 +1,90 @@
1
+ class Aduki::AttrFinder
2
+ if defined? ActiveSupport
3
+ def self.camelize str ; str.to_s.camelize ; end
4
+ def self.singularize str ; str.to_s.singularize ; end
5
+ def self.pluralize str ; str.to_s.pluralize ; end
6
+ else
7
+ def self.camelize str
8
+ str.split(/\//).map { |s| s.gsub(/(^|_)([a-z])/) { |m| $2.upcase } }.join("::")
9
+ end
10
+ def self.singularize str
11
+ str.to_s.gsub(/ies$/, "y").gsub(/s$/, '')
12
+ end
13
+ def self.pluralize str
14
+ str.to_s.gsub(/y$/, "ies").gsub(/([^s])$/, '\1s')
15
+ end
16
+ end
17
+
18
+ def self.hashify_args a
19
+ return a.first if a.first.is_a? Hash
20
+ a.inject({ }) { |hash, arg|
21
+ if arg.is_a?(Hash)
22
+ hash.merge arg
23
+ else
24
+ hash[arg] = camelize arg.to_s
25
+ hash
26
+ end
27
+ }
28
+ end
29
+
30
+ def self.attr_finders_text finder, id, *args
31
+ hashify_args(args).map { |name, klass|
32
+ attr_finder_text finder, id, name, klass
33
+ }.join("\n")
34
+ end
35
+
36
+ def self.attr_finder_text finder, id, name, klass
37
+ id_method = "#{name}_#{id}"
38
+ <<EVAL
39
+ remove_method :#{id_method} if method_defined?(:#{id_method})
40
+ remove_method :#{id_method}= if method_defined?(:#{id_method}=)
41
+ remove_method :#{name} if method_defined?(:#{name})
42
+ remove_method :#{name}= if method_defined?(:#{name}=)
43
+
44
+ attr_reader :#{id_method}
45
+
46
+ def #{id_method}= x
47
+ @#{id_method}= x
48
+ @#{name} = nil
49
+ end
50
+
51
+ def #{name}
52
+ @#{name} ||= #{klass}.#{finder}(@#{id_method}) unless @#{id_method}.nil? || @#{id_method} == ''
53
+ end
54
+
55
+ def #{name}= x
56
+ @#{name} = x
57
+ @#{id_method} = x ? x.#{id} : nil
58
+ end
59
+ EVAL
60
+ end
61
+
62
+ def self.one2many_attr_finder_text finder, id, name, options={ }
63
+ singular = singularize name.to_s
64
+ klass = options[:class_name] || camelize(singular)
65
+ id_method = "#{singular}_#{pluralize id}"
66
+ <<EVAL
67
+ remove_method :#{id_method} if method_defined?(:#{id_method})
68
+ remove_method :#{id_method}= if method_defined?(:#{id_method}=)
69
+ remove_method :#{name} if method_defined?(:#{name})
70
+ remove_method :#{name}= if method_defined?(:#{name}=)
71
+
72
+ attr_reader :#{id_method}
73
+
74
+ def #{id_method}= x
75
+ @#{id_method} = x
76
+ @#{name} = nil
77
+ end
78
+
79
+ def #{name}
80
+ @#{name} ||= #{klass}.#{finder} @#{id_method} unless @#{id_method}.nil?
81
+ end
82
+
83
+ def #{name}= x
84
+ @#{id_method} = x ? x.map(&:#{id}) : nil
85
+ @#{name} = x
86
+ end
87
+ EVAL
88
+ end
89
+
90
+ end
@@ -0,0 +1,33 @@
1
+ require 'aduki'
2
+
3
+ class Aduki::RecursiveHash < Hash
4
+ def []= key, value
5
+ return super(key, value) unless key.is_a? String
6
+
7
+ k0, k1 = key.split(/\./, 2)
8
+
9
+ if k0.match(/\[\d+\]$/)
10
+ getter = k0.gsub(/\[\d+\]$/, '')
11
+ index = k0.gsub(/.*\[(\d+)\]$/, '\1').to_i
12
+ subarray = self[getter] || []
13
+ if k1
14
+ subarray[index] ||= Aduki::RecursiveHash.new
15
+ subarray[index][k1] = value
16
+ else
17
+ subarray[index] = value
18
+ end
19
+ super getter, subarray
20
+ else
21
+ return super(key, value) if k1.nil?
22
+ existing = self[k0]
23
+ subhash = (existing.is_a? Hash) ? existing : Aduki::RecursiveHash.new
24
+ subhash[k1] = value
25
+ super k0, subhash
26
+ end
27
+ end
28
+
29
+ def copy other
30
+ other.each { |k, v| self[k]= v }
31
+ self
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Aduki
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,21 @@
1
+ require "aduki"
2
+ require "spec_helper"
3
+
4
+ describe Aduki::AttrFinder do
5
+ it "should assign a value using attr_finder" do
6
+ props = {
7
+ "name" => "Eamonn de Valera",
8
+ "city_name" => "dublin",
9
+ "gift_names[0]" => "dinner",
10
+ "gift_names[1]" => "whiskey",
11
+ "gift_names[2]" => "cigars",
12
+ }
13
+
14
+ politician = Politician.new props
15
+
16
+ expect(politician.name).to eq "Eamonn de Valera"
17
+ expect(politician.city).to eq City::CITIES["dublin"]
18
+ expect(politician.city.name).to eq "dublin"
19
+ expect(politician.gifts).to eq [Gift.lookup("dinner"), Gift.lookup("whiskey"), Gift.lookup("cigars")]
20
+ end
21
+ end
@@ -12,7 +12,7 @@ describe Aduki::Initializer do
12
12
  contraption = MachineBuilder.new props
13
13
 
14
14
  expect(contraption.name).to eq "Anna Livia"
15
- expect(contraption.city).to eq CITIES["dublin"]
16
- expect(contraption.city.name).to eq "Dublin"
15
+ expect(contraption.city).to eq City::CITIES["dublin"]
16
+ expect(contraption.city.name).to eq "dublin"
17
17
  end
18
18
  end
@@ -0,0 +1,136 @@
1
+ require "aduki"
2
+ require "aduki/attr_finder"
3
+ require "spec_helper"
4
+
5
+ describe Aduki::AttrFinder do
6
+ it "generates code for a single attribute" do
7
+ txt = Aduki::AttrFinder.attr_finder_text :find, :id, "widget_holder", "WidgetHolder"
8
+ expected = <<EXPECTED
9
+ remove_method :widget_holder_id if method_defined?(:widget_holder_id)
10
+ remove_method :widget_holder_id= if method_defined?(:widget_holder_id=)
11
+ remove_method :widget_holder if method_defined?(:widget_holder)
12
+ remove_method :widget_holder= if method_defined?(:widget_holder=)
13
+
14
+ attr_reader :widget_holder_id
15
+
16
+ def widget_holder_id= x
17
+ @widget_holder_id= x
18
+ @widget_holder = nil
19
+ end
20
+
21
+ def widget_holder
22
+ @widget_holder ||= WidgetHolder.find(@widget_holder_id) unless @widget_holder_id.nil? || @widget_holder_id == ''
23
+ end
24
+
25
+ def widget_holder= x
26
+ @widget_holder = x
27
+ @widget_holder_id = x ? x.id : nil
28
+ end
29
+ EXPECTED
30
+
31
+ expect(txt).to eq expected
32
+ end
33
+
34
+ it "generates code for multiple attributes" do
35
+ txt = Aduki::AttrFinder.attr_finders_text :open, :time, :happy_hour, widget: "WidgetHolder::Base"
36
+ expected = <<EXPECTED
37
+ remove_method :happy_hour_time if method_defined?(:happy_hour_time)
38
+ remove_method :happy_hour_time= if method_defined?(:happy_hour_time=)
39
+ remove_method :happy_hour if method_defined?(:happy_hour)
40
+ remove_method :happy_hour= if method_defined?(:happy_hour=)
41
+
42
+ attr_reader :happy_hour_time
43
+
44
+ def happy_hour_time= x
45
+ @happy_hour_time= x
46
+ @happy_hour = nil
47
+ end
48
+
49
+ def happy_hour
50
+ @happy_hour ||= HappyHour.open(@happy_hour_time) unless @happy_hour_time.nil? || @happy_hour_time == ''
51
+ end
52
+
53
+ def happy_hour= x
54
+ @happy_hour = x
55
+ @happy_hour_time = x ? x.time : nil
56
+ end
57
+
58
+ remove_method :widget_time if method_defined?(:widget_time)
59
+ remove_method :widget_time= if method_defined?(:widget_time=)
60
+ remove_method :widget if method_defined?(:widget)
61
+ remove_method :widget= if method_defined?(:widget=)
62
+
63
+ attr_reader :widget_time
64
+
65
+ def widget_time= x
66
+ @widget_time= x
67
+ @widget = nil
68
+ end
69
+
70
+ def widget
71
+ @widget ||= WidgetHolder::Base.open(@widget_time) unless @widget_time.nil? || @widget_time == ''
72
+ end
73
+
74
+ def widget= x
75
+ @widget = x
76
+ @widget_time = x ? x.time : nil
77
+ end
78
+ EXPECTED
79
+
80
+ expect(txt).to eq expected
81
+ end
82
+
83
+ it "generates a one-to-many finder" do
84
+ txt = Aduki::AttrFinder.one2many_attr_finder_text :purchase, :price, :birthday_gifts
85
+ expected = <<EXPECTED
86
+ remove_method :birthday_gift_prices if method_defined?(:birthday_gift_prices)
87
+ remove_method :birthday_gift_prices= if method_defined?(:birthday_gift_prices=)
88
+ remove_method :birthday_gifts if method_defined?(:birthday_gifts)
89
+ remove_method :birthday_gifts= if method_defined?(:birthday_gifts=)
90
+
91
+ attr_reader :birthday_gift_prices
92
+
93
+ def birthday_gift_prices= x
94
+ @birthday_gift_prices = x
95
+ @birthday_gifts = nil
96
+ end
97
+
98
+ def birthday_gifts
99
+ @birthday_gifts ||= BirthdayGift.purchase @birthday_gift_prices unless @birthday_gift_prices.nil?
100
+ end
101
+
102
+ def birthday_gifts= x
103
+ @birthday_gift_prices = x ? x.map(&:price) : nil
104
+ @birthday_gifts = x
105
+ end
106
+ EXPECTED
107
+ expect(txt).to eq expected
108
+ end
109
+
110
+ it "generates a one-to-many finder with alternative class name" do
111
+ txt = Aduki::AttrFinder.one2many_attr_finder_text :purchase, :price, :birthday_gifts, class_name: "ToyShop"
112
+ expected = <<EXPECTED
113
+ remove_method :birthday_gift_prices if method_defined?(:birthday_gift_prices)
114
+ remove_method :birthday_gift_prices= if method_defined?(:birthday_gift_prices=)
115
+ remove_method :birthday_gifts if method_defined?(:birthday_gifts)
116
+ remove_method :birthday_gifts= if method_defined?(:birthday_gifts=)
117
+
118
+ attr_reader :birthday_gift_prices
119
+
120
+ def birthday_gift_prices= x
121
+ @birthday_gift_prices = x
122
+ @birthday_gifts = nil
123
+ end
124
+
125
+ def birthday_gifts
126
+ @birthday_gifts ||= ToyShop.purchase @birthday_gift_prices unless @birthday_gift_prices.nil?
127
+ end
128
+
129
+ def birthday_gifts= x
130
+ @birthday_gift_prices = x ? x.map(&:price) : nil
131
+ @birthday_gifts = x
132
+ end
133
+ EXPECTED
134
+ expect(txt).to eq expected
135
+ end
136
+ end
@@ -252,7 +252,7 @@ describe Aduki::Initializer do
252
252
  expect(model.gadget.speaker.threads).to eq [ 12.4, 8.16, 21.42 ]
253
253
  end
254
254
 
255
- it "should handle pre-initialized hashes with a previously-set array type" do
255
+ it "should handle pre-initialized hashes" do
256
256
  props = {
257
257
  "name" => "Brackish Water",
258
258
  "gadget.name" => "The Loud Gadget",
@@ -265,4 +265,105 @@ describe Aduki::Initializer do
265
265
 
266
266
  expect(model.gadget.variables).to eq({ "x"=> "29", "y" => "12.4", "z" => "8.16"})
267
267
  end
268
+
269
+ it "should handle pre-initialized hashes with more complex subkeys" do
270
+ props = {
271
+ "name" => "Brackish Water",
272
+ "gadget.name" => "The Loud Gadget",
273
+ "gadget.variables.x[4]" => "24",
274
+ "gadget.variables.x[7]" => "27",
275
+ "gadget.variables.x[3].length" => "3",
276
+ "gadget.variables.x[3].width" => "13",
277
+ "gadget.variables.x[3].depth[0]" => "23",
278
+ "gadget.variables.x[3].depth[1].high" => "23+10",
279
+ "gadget.variables.x[3].depth[1].low" => "23-10",
280
+ "gadget.variables.x[3].depth[2]" => "33",
281
+ "gadget.variables.y.fortune" => "flavours",
282
+ "gadget.variables.y.help" => "F1",
283
+ "gadget.variables.y.pandora" => "boxy",
284
+ "gadget.variables.z" => "8.16",
285
+ }
286
+
287
+ model = Model.new props
288
+
289
+ expected = {
290
+ "x"=> [
291
+ nil,
292
+ nil,
293
+ nil,
294
+ {
295
+ "length" => "3",
296
+ "width" => "13",
297
+ "depth" => [
298
+ "23",
299
+ { "high" => "23+10", "low" => "23-10"},
300
+ "33"
301
+ ],
302
+ },
303
+ "24",
304
+ nil,
305
+ nil,
306
+ "27"
307
+ ],
308
+ "y" => {
309
+ "fortune" => "flavours",
310
+ "help" => "F1",
311
+ "pandora" => "boxy"},
312
+ "z" => "8.16"
313
+ }
314
+ expect(model.gadget.variables).to eq expected
315
+ end
316
+
317
+ it "creates a new hash by recursively splitting string keys on separator-character" do
318
+ props = {
319
+ "name" => "Brackish Water",
320
+ "gadget.name" => "The Loud Gadget",
321
+ "gadget.variables.x[4]" => "24",
322
+ "gadget.variables.x[7]" => "27",
323
+ "gadget.variables.x[3].length" => "3",
324
+ "gadget.variables.x[3].width" => "13",
325
+ "gadget.variables.x[3].depth[0]" => "23",
326
+ "gadget.variables.x[3].depth[1].high" => "23+10",
327
+ "gadget.variables.x[3].depth[1].low" => "23-10",
328
+ "gadget.variables.x[3].depth[2]" => "33",
329
+ "gadget.variables.y.fortune" => "flavours",
330
+ "gadget.variables.y.help" => "F1",
331
+ "gadget.variables.y.pandora" => "boxy",
332
+ "gadget.variables.z" => "8.16",
333
+ }
334
+
335
+ hash = Aduki::RecursiveHash.new.copy props
336
+ expected = {
337
+ "name" => "Brackish Water",
338
+ "gadget" => {
339
+ "name" => "The Loud Gadget",
340
+ "variables" => {
341
+ "x"=> [
342
+ nil,
343
+ nil,
344
+ nil,
345
+ {
346
+ "length" => "3",
347
+ "width" => "13",
348
+ "depth" => [
349
+ "23",
350
+ { "high" => "23+10", "low" => "23-10"},
351
+ "33"
352
+ ],
353
+ },
354
+ "24",
355
+ nil,
356
+ nil,
357
+ "27"
358
+ ],
359
+ "y" => {
360
+ "fortune" => "flavours",
361
+ "help" => "F1",
362
+ "pandora" => "boxy"},
363
+ "z" => "8.16"
364
+ }
365
+ }
366
+ }
367
+ expect(hash).to eq expected
368
+ end
268
369
  end
@@ -83,4 +83,43 @@ describe Aduki::Initializer do
83
83
  expect(model.gadget.speaker.ohms). to eq "29"
84
84
  expect(model.gadget.speaker.diameter).to eq "large"
85
85
  end
86
+
87
+
88
+ it "merges a hash attribute" do
89
+ props = {
90
+ "name" => "Brackish Water",
91
+ "gadget.name" => "The Loud Gadget",
92
+ "gadget.variables.x" => "29",
93
+ "gadget.variables.y" => "12.4",
94
+ "gadget.variables.z" => "8.16",
95
+ }
96
+
97
+ model = Model.new props
98
+
99
+ more_props = {
100
+ "gadget.variables.x.left" => "strong",
101
+ "gadget.variables.x.right" => "central",
102
+ "gadget.variables.y.left" => "weak",
103
+ "gadget.variables.y.right" => "mirror",
104
+ "gadget.variables.other" => "central",
105
+ }
106
+
107
+ Aduki.apply_attributes model, more_props
108
+
109
+ expected = {
110
+ "x"=> {
111
+ "left" => "strong",
112
+ "right" => "central",
113
+ },
114
+ "y" => {
115
+ "left" => "weak",
116
+ "right" => "mirror",
117
+ },
118
+ "z" => "8.16",
119
+ "other" => "central"
120
+ }
121
+
122
+ expect(model.gadget.variables).to eq expected
123
+ end
124
+
86
125
  end
@@ -6,18 +6,46 @@ RSpec.configure do |config|
6
6
  end
7
7
 
8
8
 
9
- CITIES = { }
10
-
11
9
  class City < Struct.new(:name)
10
+ CITIES = { }
12
11
  def self.aduki_find value
13
12
  CITIES[value]
14
13
  end
14
+
15
+ def register
16
+ CITIES[name] = self
17
+ end
18
+ end
19
+
20
+ City.new("paris" ).register
21
+ City.new("madrid" ).register
22
+ City.new("stockholm").register
23
+ City.new("dublin" ).register
24
+
25
+ class Gift
26
+ GIFTS = { }
27
+ include Aduki::Initializer
28
+ attr_accessor :name, :price
29
+ def self.lookup name
30
+ name.is_a?(String) ? GIFTS[name] : name.map { |n| GIFTS[n] }
31
+ end
32
+ def register
33
+ GIFTS[name] = self
34
+ end
15
35
  end
16
36
 
17
- CITIES["paris"] = City.new "Paris"
18
- CITIES["madrid"] = City.new "Madrid"
19
- CITIES["stockholm"] = City.new "Stockholm"
20
- CITIES["dublin"] = City.new "Dublin"
37
+ Gift.new(name: "dinner" , price: :cheap ).register
38
+ Gift.new(name: "massage" , price: :cheap ).register
39
+ Gift.new(name: "med_cruise", price: :expensive).register
40
+ Gift.new(name: "whiskey" , price: :medium ).register
41
+ Gift.new(name: "cigars" , price: :medium ).register
42
+
43
+ class Politician
44
+ include Aduki::Initializer
45
+ attr_accessor :name
46
+ attr_finder :aduki_find, :name, :city
47
+ attr_many_finder :lookup, :name, :gifts
48
+ end
21
49
 
22
50
  class Contraption
23
51
  include Aduki::Initializer
@@ -52,7 +80,7 @@ class Gadget
52
80
  attr_accessor :name, :price, :supplier, :variables
53
81
  attr_writer :wattage
54
82
  aduki_initialize :speaker, Speaker
55
- aduki_initialize :variables, Hash, nil
83
+ aduki_initialize :variables, Aduki::RecursiveHash, nil
56
84
 
57
85
  def watts
58
86
  @wattage
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aduki
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Conan Dalton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-03 00:00:00.000000000 Z
11
+ date: 2016-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -53,9 +53,13 @@ files:
53
53
  - Rakefile
54
54
  - aduki.gemspec
55
55
  - lib/aduki.rb
56
+ - lib/aduki/attr_finder.rb
57
+ - lib/aduki/recursive_hash.rb
56
58
  - lib/aduki/version.rb
57
59
  - spec/array_attribute_spec.rb
60
+ - spec/attr_finder_spec.rb
58
61
  - spec/custom_builder_spec.rb
62
+ - spec/finder_spec.rb
59
63
  - spec/initializer_spec.rb
60
64
  - spec/merge_attributes_spec.rb
61
65
  - spec/model.rb
@@ -90,7 +94,9 @@ specification_version: 4
90
94
  summary: set object attributes recursively from an attributes hash
91
95
  test_files:
92
96
  - spec/array_attribute_spec.rb
97
+ - spec/attr_finder_spec.rb
93
98
  - spec/custom_builder_spec.rb
99
+ - spec/finder_spec.rb
94
100
  - spec/initializer_spec.rb
95
101
  - spec/merge_attributes_spec.rb
96
102
  - spec/model.rb