aduki 0.1.3 → 0.2.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: 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