moosex 0.0.10 → 0.0.11

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: 2e60a3c50ecdccca330d91d980b5512a4ff2134d
4
- data.tar.gz: b86c1f9dfdfcf180b05d4c4cde80ccec38c9ad83
3
+ metadata.gz: fd8eb35f9d9028cc9f148b60f56f49ae49b2472f
4
+ data.tar.gz: 0c94cb4df401dd137d59ca3133917ddf7f195d9d
5
5
  SHA512:
6
- metadata.gz: 27668b133f039c5bc9f99e8b16f291071a213d9cb1e1bf040828953c0d17f2ece5768365a118c46feb4cba8d5f03e4d3b5af3c0f910dce565f4a32ddec820167
7
- data.tar.gz: ee19e17a6877e20fb57c54490b109d8aca8cc6abe4a4686e4f69bbc13ad9c4e411373f7293579b49a73e3a170a1be7c9fe45a3493a3f793d45b439a6397a6055
6
+ metadata.gz: 1feb33f6f36589c92562a2c15a89e71a8267dab34e4742ef9b5d3743f1fb2be1828a5e653d9926fa22ec68a697731d31856845b16799d0dee70163ed20e28dc4
7
+ data.tar.gz: d58b9b922630abd5ff3160641661887d9bdde81df95afa4f413cc13cb7430b82a1b08ce15e7091251cfaddf19c658f55640436b5170fe32491190e657f4eef9f
data/Changelog CHANGED
@@ -1,3 +1,8 @@
1
+ 0.0.11 - 2013-02-04
2
+ - basic support to after/before/around #12
3
+ - improve exception #15
4
+ - improve type system via MooseX::Types #16
5
+
1
6
  0.0.10 - 2014-02-03
2
7
  - improve readme #2014-02-01
3
8
  - BUILDARGS should accept different signatures #36
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- moosex (0.0.10)
4
+ moosex (0.0.11)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MooseX
2
2
 
3
- A postmodern object DSL for Ruby [![Build Status](https://travis-ci.org/peczenyj/MooseX.png)](https://travis-ci.org/peczenyj/MooseX)
3
+ A postmodern object DSL for Ruby [![Build Status](https://travis-ci.org/peczenyj/MooseX.png)](https://travis-ci.org/peczenyj/MooseX) [![Gem Version](https://badge.fury.io/rb/moosex.png)](http://badge.fury.io/rb/moosex)
4
4
 
5
5
  THIS MODULE IS EXPERIMENTAL YET! BE CAREFUL!
6
6
 
@@ -137,7 +137,6 @@ You can specify your own kind of type validation.
137
137
  raise "bar should respond to to_sym method!"
138
138
  end
139
139
  end,
140
- end
141
140
  ```
142
141
 
143
142
  Important: if you access the attribute instance name using @attribute_name= you loose the type check feature. You need always set/get the attribute value using the acessors generated by MooseX.
@@ -154,6 +153,18 @@ or
154
153
  default: lambda{ MyObject.new },
155
154
  ```
156
155
 
156
+ ### required
157
+
158
+ if true, the constructor will raise error if this attribute was not present.
159
+
160
+ ```ruby
161
+ required: true,
162
+ ```
163
+
164
+ if this attribute has a default value, we will initialize with this value and no exception will be raised.
165
+
166
+ Optional.
167
+
157
168
  ### coerce
158
169
 
159
170
  You can try to coerce the attribute value by a lambda before the type check phase. For example you can do
@@ -195,7 +206,9 @@ Optional.
195
206
  You can specify one lambda or method name to be executed in each writter ( if coerce and type check does not raise any exception ). The trigger will be called in each setter and in the constructor if we do not use the default value. Useful to add a logging operation or some complex validation.
196
207
 
197
208
  ```ruby
198
- trigger: lambda {|object, new_value| object.logger.log "change the attribute value to #{new_value}" }
209
+ trigger: lambda do |object, new_value|
210
+ object.logger.log "change the attribute value to #{new_value}"
211
+ end
199
212
  ```
200
213
  or
201
214
  ```ruby
@@ -282,7 +295,7 @@ class Foo
282
295
  is: :rw,
283
296
  writter: :x=,
284
297
  reader: :x,
285
- init_art: :x,
298
+ init_arg: :x,
286
299
  }
287
300
  end
288
301
 
@@ -341,6 +354,220 @@ end
341
354
  ```
342
355
  Optional.
343
356
 
357
+ ## Hooks: after/before/around
358
+
359
+ Another great feature imported from Moose are the hooks after/before/around one method. You can run an arbitrary code, for example:
360
+
361
+ ```ruby
362
+ class Point
363
+ include MooseX
364
+
365
+ has [:x, :y ], { is: :rw, required: true }
366
+
367
+ def clear!
368
+ self.x = 0
369
+ self.y = 0
370
+ end
371
+ end
372
+
373
+ class Point3D < Point
374
+
375
+ has z: { is: :rw, required: true }
376
+
377
+ after :clear! do |object|
378
+ object.z = 0
379
+ end
380
+ end
381
+ ```
382
+
383
+ instead redefine the 'clear!' method in the subclass, we just add a piece of code, a lambda, and it will be executed after the normal 'clear!' method.
384
+
385
+ ### after
386
+
387
+ The after hook should receive the name of the method as a Symbol and a lambda. This lambda will, in the argument list, one reference for the object (self) and the rest of the arguments. This will redefine the the original method, add the code to run after the method. The after does not affect the return value of the original method, if you need this, use the 'around' hook.
388
+
389
+ ### before
390
+
391
+ The before hook should receive the name of the method as a Symbol and a lambda. This lambda will, in the argument list, one reference for the object (self) and the rest of the arguments. This will redefine the the original method, add the code to run before the method.
392
+
393
+ A good example should be logging:
394
+
395
+ ```ruby
396
+ class Point
397
+ include MooseX
398
+
399
+ def my_method(x)
400
+ # do something
401
+ end
402
+
403
+ before :my_method do |object, x|
404
+ puts "#{Time.now} before my_method(#{x})"
405
+ end
406
+ after :my_method do |object, x|
407
+ puts "#{Time.now} after my_method(#{x})"
408
+ end
409
+ end
410
+ ```
411
+
412
+ ### around
413
+
414
+ The around hook is agressive: it will substitute the original method for a lambda. This lambda will receive the original method, a reference for the object and the argument list
415
+
416
+ ```ruby
417
+ around(:sum) do |original_method, object, a,b,c|
418
+ result = original_method.bind(object).call(a,b,c)
419
+ result + 1
420
+ end
421
+ ```
422
+
423
+ it is useful to manipulate the return value if you need.
424
+
425
+ ## Types
426
+
427
+ MooseX has a built-in type system to be helpful in many circunstances. How many times you need check if some argument is_a? Something? Or it respond_to? :some_method ? Now it is over. If you include the MooseX::Types module in your MooseX class you can use:
428
+
429
+ ### isAny
430
+
431
+ will accept any type. Useful to combine with other types.
432
+
433
+ ```ruby
434
+ has x: { is: :rw, isa: isAny }
435
+ ```
436
+
437
+ ### isConstant
438
+
439
+ will verify using :=== if the value is equal to some contant
440
+
441
+ ```ruby
442
+ has x: { is: :rw, isa: isConstant(1) }
443
+ ```
444
+
445
+ ### isType, isInstanceOf and isConsumerOf
446
+
447
+ will verify the type using is_a? method. should receive a Class. isInstanceOf is an alias, to be used with Classes and isConsumerOf is for Modules.
448
+
449
+ ```ruby
450
+ has x: { is: :rw, isa: isConsumerOf(Enumerable) }
451
+ ```
452
+
453
+ ### hasMethods
454
+
455
+ will verify if the value respond_to? for one or more methods.
456
+
457
+ ```ruby
458
+ has x: { is: :rw, isa: hasMethods(:to_s) }
459
+ ```
460
+ ### isEnum
461
+
462
+ verify if the value is part of one enumeration of constants.
463
+
464
+ ```ruby
465
+ has x: { is: :rw, isa: isEnum(:black, :white, :red, :green, :yellow) }
466
+ ```
467
+
468
+ ### isMaybe(type)
469
+
470
+ verify if the value isa type or is nil. You can combine with other types.
471
+
472
+ ```ruby
473
+ has x: { is: :rw, isa: isMaybe(Integer) } # accepts 1,2,3... or nil
474
+ ```
475
+
476
+ ### isNot(type)
477
+
478
+ will revert the type check. Useful to combine with other types
479
+
480
+ ```ruby
481
+ has x: {
482
+ is: :rw, # x will accept any values EXCEPT :black, :white...
483
+ isa: isNot(isEnum(:black, :white, :red, :green, :yellow))
484
+ }
485
+ ```
486
+
487
+ ### isArray(type)
488
+
489
+ Will verify if the value is an Array. Can receive one extra type, and we will verify each element inside the array againt this type, or Any if we not specify the type.
490
+
491
+ ```ruby
492
+ has x: {
493
+ is: :rw,
494
+ isa: isArray() # will accept any array
495
+ }
496
+ has y: {
497
+ is: :rw, # this is a more complex type
498
+ isa: isArray(isArray(isMaybe(Integer)))
499
+ }
500
+ ```
501
+
502
+ ### isHash(type=>type)
503
+
504
+ similar to isArray. if you do not specify a pair of types, it will check only if the value is_a? Hash. Otherwise we will verify each pair key/value.
505
+
506
+ ```ruby
507
+ has x: {
508
+ is: :rw,
509
+ isa: isHash() # will accept any Hash
510
+ }
511
+ has y: {
512
+ is: :rw, # this is a more complex type
513
+ isa: isHash(Integer => isArray(isMaybe(Integer)))
514
+ }
515
+ ```
516
+
517
+ ## isSet(type)
518
+
519
+ similar to isSet. the difference is: it will raise one exception if there are non unique elements in this array.
520
+
521
+ ```ruby
522
+ has x: {
523
+ is: :rw,
524
+ isa: isSet(Integer) # will accept [1,2,3] but not [1,1,1]
525
+ }
526
+ ```
527
+
528
+ ## isTuple(types)
529
+
530
+ similar to isArray, Tuples are Arrays with fixed size. We will verify the type of each element.
531
+
532
+ For example, to specify one tuple with three elements, the first is Integer, the second is a Symbol and the las should be a String or nil:
533
+
534
+ ```ruby
535
+ has x: {
536
+ is: :rw,
537
+ isa: isTuple(Integer, Symbol, isMaybe(String))
538
+ }
539
+ ```
540
+
541
+ ### isAllOf(types)
542
+
543
+ will combine all types and will fail if one of the condition fails.
544
+
545
+ ```ruby
546
+ has x: {
547
+ is: :rw,
548
+ isa: isAllOf(
549
+ hasMethods(:foo, :bar), # x should has foo and bar methods
550
+ isConsumerOf(Enumerable), # AND should be an Enumerable
551
+ isNot(SomeForbiddenClass), # AND and should not be an SomeForbiddenClass
552
+ )
553
+ }
554
+ ```
555
+
556
+ ### isAnyOf(types)
557
+
558
+ will combine all types and will fail if all of the condition fails.
559
+
560
+ ```ruby
561
+ has x: {
562
+ is: :rw,
563
+ isa: isAnyOf(
564
+ hasMethods(:foo, :bar), # x should has foo and bar methods
565
+ isEnum(1,2,3,4), # OR be 1,2,3 or 4
566
+ isHash(isAny => Integer) # OR be an Hash of any type => Integers
567
+ )
568
+ }
569
+ ```
570
+
344
571
  ## BUILD
345
572
 
346
573
  If you need run some code after the creation of the object (like some extra validation), you should override the BUILD method.
@@ -362,7 +589,7 @@ end
362
589
 
363
590
  b1 = BuildExample.new(x: 1, y: 2) # will create the object
364
591
  b2 = BuildExample.new(x: 1, y: 1) # will raise the exception!
365
- ```ruby
592
+ ```
366
593
 
367
594
  ### BUILDARGS
368
595
 
data/lib/moosex.rb CHANGED
@@ -6,9 +6,10 @@
6
6
  # License:: MIT
7
7
  #
8
8
  require "moosex/version"
9
+ require "moosex/types"
9
10
 
10
11
  module MooseX
11
-
12
+
12
13
  def MooseX.included(c)
13
14
 
14
15
  c.extend(MooseX::Core)
@@ -38,7 +39,7 @@ module MooseX
38
39
  subclass.class_exec do
39
40
  old_meta = subclass.__meta
40
41
 
41
- meta = MooseX::Meta.new(old_meta.attrs)
42
+ meta = MooseX::Meta.new(old_meta)
42
43
 
43
44
  define_singleton_method(:__meta) { meta }
44
45
  end
@@ -47,22 +48,78 @@ module MooseX
47
48
  end
48
49
 
49
50
  class Meta
50
- attr_reader :attrs
51
- def initialize(attrs=[])
52
- @attrs = attrs.map{|att| att.clone }
51
+ attr_reader :attrs, :after, :before, :around
52
+
53
+ def initialize(old_meta=nil)
54
+ @attrs = []
55
+ @after = Hash.new {|hash, key| hash[key] = []}
56
+ @before= Hash.new {|hash, key| hash[key] = []}
57
+ @around= Hash.new {|hash, key| hash[key] = []}
58
+
59
+ if old_meta
60
+ @attrs = old_meta.attrs.map{|att| att.clone }
61
+ @after.merge! old_meta.after.clone
62
+ @before.merge! old_meta.before.clone
63
+ @around.merge! old_meta.around.clone
64
+ end
53
65
  end
54
66
 
55
67
  def add(attr)
56
68
  @attrs << attr
57
69
  end
70
+
71
+ def add_after(method, block)
72
+ @after[method] << MooseX::Hook::After.new(method, block)
73
+ end
58
74
 
75
+ def add_before(method, block)
76
+ @before[method] << MooseX::Hook::Before.new(method, block)
77
+ end
78
+
79
+ def add_around(method, block)
80
+ @around[method] << MooseX::Hook::Around.new(method, block)
81
+ end
82
+
59
83
  def init(object, args)
60
84
  @attrs.each{ |attr| attr.init(object, args) }
61
85
  end
62
86
  end
63
87
 
64
88
  module Core
65
-
89
+ def after(method_name, &block)
90
+ method = instance_method method_name
91
+
92
+ define_method method_name do |*args|
93
+ result = method.bind(self).call(*args)
94
+ block.call(self,*args)
95
+ result
96
+ end
97
+
98
+ __meta.add_after(method_name, block)
99
+ end
100
+
101
+ def before(method_name, &block)
102
+ method = instance_method method_name
103
+
104
+ define_method method_name do |*args|
105
+ block.call(self,*args)
106
+ method.bind(self).call(*args)
107
+ end
108
+
109
+ __meta.add_before(method_name, block)
110
+ end
111
+
112
+ def around(method_name, &block)
113
+ method = instance_method method_name
114
+
115
+ define_method method_name do |*args|
116
+
117
+ block.call(method, self,*args)
118
+
119
+ end
120
+ __meta.add_around(method, block)
121
+ end
122
+
66
123
  def has(attr_name, attr_options = {})
67
124
  if attr_name.is_a? Array
68
125
  attr_name.each do |attr|
@@ -92,7 +149,31 @@ module MooseX
92
149
  end
93
150
  end
94
151
 
152
+ module Hook
153
+ class Base
154
+ attr_reader :method, :block
155
+ def initialize(method, block)
156
+ @method= method
157
+ @block = block
158
+ end
159
+ end
160
+
161
+ class After < Base
162
+ end
163
+
164
+ class Before < Base
165
+ end
166
+
167
+ class Around < Base
168
+ end
169
+ end
170
+
171
+ class InvalidAttributeError < TypeError
172
+
173
+ end
174
+
95
175
  class Attribute
176
+ include MooseX::Types
96
177
 
97
178
  attr_reader :attr_symbol, :is, :reader, :writter, :lazy, :builder, :methods
98
179
  DEFAULTS= {
@@ -100,7 +181,7 @@ module MooseX
100
181
  clearer: false,
101
182
  required: false,
102
183
  predicate: false,
103
- isa: lambda { |value| true },
184
+ isa: isAny,
104
185
  handles: {},
105
186
  trigger: lambda {|object,value|}, # TODO: implement
106
187
  coerce: lambda {|object| object}, # TODO: implement
@@ -111,7 +192,7 @@ module MooseX
111
192
  VALIDATE = {
112
193
  is: lambda do |is, field_name|
113
194
  unless [:rw, :rwp, :ro, :lazy, :private].include?(is)
114
- raise "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy"
195
+ raise InvalidAttributeError, "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy"
115
196
  end
116
197
  end,
117
198
  };
@@ -121,13 +202,7 @@ module MooseX
121
202
  is.to_sym
122
203
  end,
123
204
  isa: lambda do |isa, field_name|
124
- return isa if isa.is_a? Proc
125
-
126
- return lambda do |new_value|
127
- unless new_value.is_a?(isa)
128
- raise "isa check for \"#{field_name}\" failed: is not instance of #{isa}!"
129
- end
130
- end
205
+ isType(isa)
131
206
  end,
132
207
  default: lambda do |default, field_name|
133
208
  return default if default.is_a? Proc
@@ -151,7 +226,7 @@ module MooseX
151
226
  predicate.to_sym
152
227
  rescue => e
153
228
  # create a nested exception here
154
- raise "cannot coerce field predicate to a symbol for #{field_name}: #{e}"
229
+ raise InvalidAttributeError, "cannot coerce field predicate to a symbol for #{field_name}: #{e}"
155
230
  end
156
231
  end,
157
232
  clearer: lambda do|clearer, field_name|
@@ -165,7 +240,7 @@ module MooseX
165
240
  clearer.to_sym
166
241
  rescue => e
167
242
  # create a nested exception here
168
- raise "cannot coerce field clearer to a symbol for #{field_name}: #{e}"
243
+ raise InvalidAttributeError, "cannot coerce field clearer to a symbol for #{field_name}: #{e}"
169
244
  end
170
245
  end,
171
246
  handles: lambda do |handles, field_name|
@@ -182,7 +257,7 @@ module MooseX
182
257
 
183
258
  if handle == BasicObject
184
259
 
185
- raise "ops, should not use BasicObject for handles in #{field_name}"
260
+ raise InvalidAttributeError, "ops, should not use BasicObject for handles in #{field_name}"
186
261
 
187
262
  elsif handle.is_a? Class
188
263
 
@@ -260,7 +335,7 @@ module MooseX
260
335
 
261
336
  REQUIRED.each { |field|
262
337
  unless o.has_key?(field)
263
- raise "field #{field} is required for Attribute #{a}"
338
+ raise InvalidAttributeError, "field #{field} is required for Attribute #{a}"
264
339
  end
265
340
  }
266
341
  COERCE.each_pair do |field, coerce|
@@ -342,19 +417,20 @@ module MooseX
342
417
  value = @default.call
343
418
  value_from_default = true
344
419
  elsif @required
345
- raise "attr \"#{@attr_symbol}\" is required"
420
+ raise InvalidAttributeError, "attr \"#{@attr_symbol}\" is required"
346
421
  else
347
422
  return
348
423
  end
349
-
350
-
351
- value = @coerce.call(value)
352
424
 
353
- @isa.call( value )
425
+ value = @coerce.call(value)
426
+ begin
427
+ @isa.call( value )
428
+ rescue MooseX::Types::TypeCheckError => e
429
+ raise MooseX::Types::TypeCheckError, "isa check for field #{attr_symbol}: #{e}"
430
+ end
354
431
  unless value_from_default
355
432
  @trigger.call(object, value)
356
433
  end
357
-
358
434
  inst_variable_name = "@#{@attr_symbol}".to_sym
359
435
  object.instance_variable_set inst_variable_name, value
360
436
  end
@@ -375,7 +451,12 @@ module MooseX
375
451
 
376
452
  value = builder.call(object)
377
453
  value = coerce.call(value)
378
- type_check.call(value)
454
+ begin
455
+ type_check.call( value )
456
+ rescue MooseX::Types::TypeCheckError => e
457
+ raise MooseX::Types::TypeCheckError, "isa check for #{inst_variable_name} from builder: #{e}"
458
+ end
459
+
379
460
  trigger.call(object, value)
380
461
  object.instance_variable_set(inst_variable_name, value)
381
462
  end
@@ -388,13 +469,18 @@ module MooseX
388
469
  end
389
470
 
390
471
  def generate_writter
472
+ writter_name = @writter
391
473
  inst_variable_name = "@#{@attr_symbol}".to_sym
392
474
  coerce = @coerce
393
475
  type_check = @isa
394
476
  trigger = @trigger
395
477
  Proc.new do |value|
396
478
  value = coerce.call(value)
397
- type_check.call(value)
479
+ begin
480
+ type_check.call( value )
481
+ rescue MooseX::Types::TypeCheckError => e
482
+ raise MooseX::Types::TypeCheckError, "isa check for #{writter_name}: #{e}"
483
+ end
398
484
  trigger.call(self,value)
399
485
  instance_variable_set inst_variable_name, value
400
486
  end