moosex 0.0.16 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -1
- data/.gitignore +1 -1
- data/.travis.yml +2 -0
- data/Changelog +7 -1
- data/Gemfile.lock +16 -2
- data/README.md +105 -30
- data/lib/moosex.rb +639 -613
- data/lib/moosex/event.rb +1 -1
- data/lib/moosex/version.rb +1 -1
- data/moosex.gemspec +2 -1
- data/samples/bank_account.rb +84 -0
- data/samples/binary_tree.rb +82 -0
- data/samples/events.rb +5 -1
- data/samples/human.rb +48 -0
- data/samples/point.rb +5 -1
- data/samples/roles.rb +15 -8
- data/spec/baserole_spec.rb +1 -1
- data/spec/baz_spec.rb +1 -1
- data/spec/build_spec.rb +1 -1
- data/spec/coerce_spec.rb +34 -0
- data/spec/complex_type_spec.rb +1 -1
- data/spec/lazy_spec.rb +1 -1
- data/spec/lol_spec.rb +1 -1
- data/spec/moosex_spec.rb +24 -1
- data/spec/point_spec.rb +1 -0
- data/spec/proxy_spec.rb +1 -1
- data/spec/role_spec.rb +1 -1
- data/spec/spec_helper.rb +17 -4
- data/spec/trigger_spec.rb +1 -1
- data/spec/types_spec.rb +1 -1
- data/spec/weak_spec.rb +43 -0
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95164f069f5f27913dfeb6140414d1b0f15e9687
|
4
|
+
data.tar.gz: 8c93aee2bfaea678c830ed0a1288e170a2bc0bf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23c4c3ff11a33e0d034621564c0c336ce42ab71e511dd04a35cb8a23e2292ac41f80097c9071f61f163461c9f0e98408ec98006342115fa23bb35d896935e7e4
|
7
|
+
data.tar.gz: 9ee664b8973c9187a510ba14dec13fc89a4c40e3a596793f0a91233dcde4e2a6e711857941432d71ca61ad2a36dab0a03009b280112a198c3a65b1c860a1bcb5
|
data/.coveralls.yml
CHANGED
@@ -1 +1 @@
|
|
1
|
-
repo_token:
|
1
|
+
repo_token: E074PGwOA6oPInUXCJnKwYt2dvkGrLyvn
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Changelog
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
0.0.17 - 2013-02-11
|
2
|
+
- has now support an override option #56
|
3
|
+
- has now support a doc option #57
|
4
|
+
- has now should not require :is => default is :rw #54
|
5
|
+
- add weak ref support #49
|
6
|
+
|
1
7
|
0.0.16 - 2013-02-07
|
2
8
|
- add currying to handles #46
|
3
9
|
- not after/before/around works well with methods who receive a block
|
@@ -68,4 +74,4 @@
|
|
68
74
|
- supports isa as class/module name or lambda
|
69
75
|
|
70
76
|
0.0.1 - 2014-01-31
|
71
|
-
- first release
|
77
|
+
- first release
|
data/Gemfile.lock
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
moosex (0.0.
|
4
|
+
moosex (0.0.17)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
coveralls (0.7.0)
|
10
|
+
multi_json (~> 1.3)
|
11
|
+
rest-client
|
12
|
+
simplecov (>= 0.7)
|
13
|
+
term-ansicolor
|
14
|
+
thor
|
9
15
|
diff-lcs (1.2.5)
|
10
16
|
docile (1.1.3)
|
17
|
+
mime-types (2.1)
|
11
18
|
multi_json (1.8.4)
|
12
19
|
rake (10.1.1)
|
20
|
+
rest-client (1.6.7)
|
21
|
+
mime-types (>= 1.16)
|
13
22
|
rspec (2.14.1)
|
14
23
|
rspec-core (~> 2.14.0)
|
15
24
|
rspec-expectations (~> 2.14.0)
|
@@ -23,13 +32,18 @@ GEM
|
|
23
32
|
multi_json
|
24
33
|
simplecov-html (~> 0.8.0)
|
25
34
|
simplecov-html (0.8.0)
|
35
|
+
term-ansicolor (1.3.0)
|
36
|
+
tins (~> 1.0)
|
37
|
+
thor (0.18.1)
|
38
|
+
tins (1.0.0)
|
26
39
|
|
27
40
|
PLATFORMS
|
28
41
|
ruby
|
29
42
|
|
30
43
|
DEPENDENCIES
|
31
44
|
bundler (~> 1.5)
|
45
|
+
coveralls (>= 0.7.0)
|
32
46
|
moosex!
|
33
47
|
rake
|
34
48
|
rspec
|
35
|
-
simplecov
|
49
|
+
simplecov (>= 0.8.2)
|
data/README.md
CHANGED
@@ -1,6 +1,40 @@
|
|
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) [![Gem Version](https://badge.fury.io/rb/moosex.png)](http://badge.fury.io/rb/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) [![Code Climate](https://codeclimate.com/github/peczenyj/MooseX.png)](https://codeclimate.com/github/peczenyj/MooseX) [![Dependency Status](https://gemnasium.com/peczenyj/MooseX.png)](https://gemnasium.com/peczenyj/MooseX) [![Coverage Status](https://coveralls.io/repos/peczenyj/MooseX/badge.png)](https://coveralls.io/r/peczenyj/MooseX) [![githalytics.com alpha](https://cruel-carlota.pagodabox.com/f51f40f92298589b598a55bc753977f9 "githalytics.com")](http://githalytics.com/peczenyj/MooseX)
|
4
|
+
## Introduction
|
5
|
+
|
6
|
+
This is another DSL for object creation, aspects, method delegation and much more. It is based on Perl Moose and Moo, two important modules who add a better way of Object Orientation development (and I enjoy A LOT). Using a declarative style, using Moose/Moo you can create attributes, methods, the entire constructor and much more. But I can't find something similar in Ruby world, so I decide port a small subset of Moose to create a powerfull DSL for object construction.
|
7
|
+
|
8
|
+
Of course, there is few similar projects in ruby like
|
9
|
+
|
10
|
+
- [Virtus](https://github.com/solnic/virtus)
|
11
|
+
- [Active Record Validations](http://edgeguides.rubyonrails.org/active_record_validations.html)
|
12
|
+
|
13
|
+
But the objetive of MooseX is different: this is a toolbox to create Classes based on DSL, with unique features like
|
14
|
+
|
15
|
+
- method delegation ( see 'handles')
|
16
|
+
- lazy attributes
|
17
|
+
- roles
|
18
|
+
- parameterized roles
|
19
|
+
- composable type check
|
20
|
+
- events
|
21
|
+
|
22
|
+
and much more.
|
23
|
+
|
24
|
+
This rubygem is based on this modules:
|
25
|
+
|
26
|
+
- [Perl Moose](http://search.cpan.org/~ether/Moose-2.1204/lib/Moose.pm)
|
27
|
+
- [Perl Moo](http://search.cpan.org/~ether/Moose-2.1204/lib/Moose.pm)
|
28
|
+
- [MooX::Types::MooseLike::Base](http://search.cpan.org/~mateu/MooX-Types-MooseLike-0.25/lib/MooX/Types/MooseLike/Base.pm)
|
29
|
+
- [MooseX::Event](http://search.cpan.org/~winter/MooseX-Event-v0.2.0/lib/MooseX/Event.pm)
|
30
|
+
- [MooseX::Role::Parameterized](http://search.cpan.org/~sartak/MooseX-Role-Parameterized-1.02/lib/MooseX/Role/Parameterized/Tutorial.pod)
|
31
|
+
|
32
|
+
See also:
|
33
|
+
|
34
|
+
- [Joose](https://code.google.com/p/joose-js/), a javascript port of Moose.
|
35
|
+
- [Perl 6](http://en.wikipedia.org/wiki/Perl_6#Object-oriented_programming) Perl 6 OO programming style.
|
36
|
+
|
37
|
+
Why MooseX? Because the namespace MooseX/MooX is open to third-party projects/plugins/extensions. You can upgrade your Moo(se) class using other components if you want. And there is one gem called 'moose' :/
|
4
38
|
|
5
39
|
THIS MODULE IS EXPERIMENTAL YET! BE CAREFUL!
|
6
40
|
|
@@ -112,9 +146,9 @@ to describe one new attribute you shoud specify some properties inside a Hash. T
|
|
112
146
|
|
113
147
|
The options for "has" are as follows:
|
114
148
|
|
115
|
-
### is
|
149
|
+
### is => ro|rw|rwp|private|lazy
|
116
150
|
|
117
|
-
**
|
151
|
+
**Important**, may be :ro, :rw, :rwp, :private or :lazy. If you not specify, we will consider :rw, with all acessors with public visibility (**NEW**).
|
118
152
|
|
119
153
|
"ro" specify a read-only attribute - generate only the reader method - you should specify the value in the constructor or using "default".
|
120
154
|
|
@@ -126,7 +160,7 @@ The options for "has" are as follows:
|
|
126
160
|
|
127
161
|
"lazy" similar to "ro", but also sets "lazy" to true and "builder" to "build_#{attribute_name}".
|
128
162
|
|
129
|
-
### isa
|
163
|
+
### isa => Class|lambda
|
130
164
|
|
131
165
|
You can specify an optional type check for the attribute. Accepts a lambda, and it must raise one exception if the type check fails. If you provides a Class or Module, we will call the 'is_a?' method in the new value againt the Class/Module. We call the type check routine on the constructor and in each call of the writter method.
|
132
166
|
|
@@ -141,7 +175,7 @@ You can specify your own kind of type validation.
|
|
141
175
|
|
142
176
|
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.
|
143
177
|
|
144
|
-
### default
|
178
|
+
### default => Constant|lambda
|
145
179
|
|
146
180
|
You can specify an optional default value to one attribute. If we don't specify in the constructor, we will initialize the attribute with this value. You also can specify one lambda to force object creation.
|
147
181
|
|
@@ -153,7 +187,7 @@ or
|
|
153
187
|
default: lambda{ MyObject.new },
|
154
188
|
```
|
155
189
|
|
156
|
-
### required
|
190
|
+
### required => true|false
|
157
191
|
|
158
192
|
if true, the constructor will raise error if this attribute was not present.
|
159
193
|
|
@@ -165,7 +199,7 @@ if this attribute has a default value, we will initialize with this value and no
|
|
165
199
|
|
166
200
|
Optional.
|
167
201
|
|
168
|
-
### coerce
|
202
|
+
### coerce => method name|lambda
|
169
203
|
|
170
204
|
You can try to coerce the attribute value by a lambda/method before the type check phase. For example you can do
|
171
205
|
|
@@ -181,7 +215,7 @@ or just
|
|
181
215
|
|
182
216
|
to force a convertion to integer. Or flatten one array, convert to symbol, etc. Optional.
|
183
217
|
|
184
|
-
### handles
|
218
|
+
### handles => Array|Hash|Class|Module
|
185
219
|
|
186
220
|
One of the greatest features in MooseX: you can inject methods and delegate the method calling to the attribute. For example, instead do this:
|
187
221
|
|
@@ -264,7 +298,7 @@ obj.my_method_1(2,3)
|
|
264
298
|
```
|
265
299
|
are equivalent.
|
266
300
|
|
267
|
-
### trigger
|
301
|
+
### trigger => method name|lambda
|
268
302
|
|
269
303
|
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.
|
270
304
|
|
@@ -290,15 +324,15 @@ has b: {
|
|
290
324
|
|
291
325
|
Optional.
|
292
326
|
|
293
|
-
### writter
|
327
|
+
### writter => true|method name
|
294
328
|
|
295
|
-
You can specify the name of the attribute acessor, default is "#{attribute_name}=".
|
329
|
+
You can specify the name of the attribute acessor, default is "#{attribute_name}=" (if true).
|
296
330
|
|
297
|
-
### reader
|
331
|
+
### reader => true|method name
|
298
332
|
|
299
|
-
You can specify the name of the attribute acessor, default is "attribute_name".
|
333
|
+
You can specify the name of the attribute acessor, default is "attribute_name" (if true).
|
300
334
|
|
301
|
-
### predicate
|
335
|
+
### predicate => true|method name
|
302
336
|
|
303
337
|
Creates a method who returns a boolean value if the attribute is defined. If true, will create one public "has_#{attribute_name}?" method by default.
|
304
338
|
|
@@ -322,7 +356,7 @@ Important: nil is different than undefined. If you do not initialize one attribu
|
|
322
356
|
|
323
357
|
Optional.
|
324
358
|
|
325
|
-
### clearer
|
359
|
+
### clearer => true| attribute name
|
326
360
|
|
327
361
|
Creates a method who will unset the attribute. If true, will create one public "clear_#{attribute_name}!" method by default. Unset in this case is not 'nil', we will remove the instance variable. For example:
|
328
362
|
|
@@ -346,7 +380,7 @@ foo.has_x? # returns false
|
|
346
380
|
```
|
347
381
|
Optional.
|
348
382
|
|
349
|
-
### init_arg
|
383
|
+
### init_arg => "new attribute name"
|
350
384
|
|
351
385
|
You can rename the attribute name in the constructor. For example:
|
352
386
|
|
@@ -367,7 +401,7 @@ foo.x # return 1
|
|
367
401
|
foo.x= 2 # will set 'secret' to 2
|
368
402
|
```
|
369
403
|
|
370
|
-
### lazy
|
404
|
+
### lazy => true|false
|
371
405
|
|
372
406
|
Another great feature: lazy attributes. If you this to true, we will wait until the first reader accessor be called to create the object using the builder method, then store the value. For example:
|
373
407
|
|
@@ -400,7 +434,7 @@ A lazy attribute needs a builder method or lambda. By default you should impleme
|
|
400
434
|
|
401
435
|
Optional.
|
402
436
|
|
403
|
-
### builder
|
437
|
+
### builder => method name|lambda
|
404
438
|
|
405
439
|
You can specify the builder name if the attribute is lazy, or you can specity one lambda. If true, the default name of the builder will be "builder_#{attribute_name}". This attribute will be ignored if the attribute is not lazy.
|
406
440
|
|
@@ -417,6 +451,37 @@ end
|
|
417
451
|
```
|
418
452
|
Optional.
|
419
453
|
|
454
|
+
### weak => true|false
|
455
|
+
|
456
|
+
If true, we will always coerce the value from default, constructor or writter to a WeakRef. Weak Reference class that allows a referenced object to be garbage-collected.
|
457
|
+
|
458
|
+
```ruby
|
459
|
+
class Foo
|
460
|
+
include MooseX
|
461
|
+
has x: { is: :rw, weak: true }
|
462
|
+
end
|
463
|
+
|
464
|
+
f = Foo.new(x: Object.new)
|
465
|
+
|
466
|
+
puts f.x.class # will be WeakRef
|
467
|
+
GC.start
|
468
|
+
puts f.x # may raise exception (recycled)
|
469
|
+
```
|
470
|
+
|
471
|
+
You should verify with `weakref_alive?` method to avoid exceptions.
|
472
|
+
|
473
|
+
Optional.
|
474
|
+
|
475
|
+
## doc => String
|
476
|
+
|
477
|
+
You can add a string metadata about the attribute. It will be stored, and you can use this in a near future.
|
478
|
+
|
479
|
+
Optional.
|
480
|
+
|
481
|
+
## override => true|false
|
482
|
+
|
483
|
+
If you need override one attribute, you should use `override: true`, or MooseX will raise one exception.
|
484
|
+
|
420
485
|
## Hooks: after/before/around
|
421
486
|
|
422
487
|
Another great feature imported from Moose are the hooks after/before/around one method. You can run an arbitrary code, for example:
|
@@ -451,11 +516,11 @@ Roles and Hooks
|
|
451
516
|
|
452
517
|
If you try to add one role to a method who does not exists yet, this will be added in the next class. BE CAREFUL, THIS IS EXPERIMENTAL! PLEASE REPORT ANY BUG IF YOU FIND!!!
|
453
518
|
|
454
|
-
### after
|
519
|
+
### after (method| ARRAY) => lambda
|
455
520
|
|
456
521
|
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.
|
457
522
|
|
458
|
-
### before
|
523
|
+
### before (method| ARRAY) => lambda
|
459
524
|
|
460
525
|
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.
|
461
526
|
|
@@ -478,7 +543,7 @@ class Point
|
|
478
543
|
end
|
479
544
|
```
|
480
545
|
|
481
|
-
### around
|
546
|
+
### around (method| ARRAY) => lambda
|
482
547
|
|
483
548
|
The around hook is agressive: it will substitute the original method for a lambda. This lambda will receive the original method as a lambda, a reference for the object and the argument list, you shuld call the method_lambda using object + arguments
|
484
549
|
|
@@ -691,9 +756,9 @@ Roles can support has to describe attributes, and you can reuse code easily.
|
|
691
756
|
You can also mark one or more methods as 'required'. When you do this, we will raise one exception if you try to create a new instance and the class does not implement it. It is a safe way to create interfaces or abstract classes. It uses respond_to? to verify.
|
692
757
|
|
693
758
|
|
694
|
-
##
|
759
|
+
## Parameterized Roles
|
695
760
|
|
696
|
-
|
761
|
+
Parameterized roles is a good way of reuse code based on roles. For example, to create one or more attributes in the class who includes our role, we just add the code to be executed in the on_init hook.
|
697
762
|
|
698
763
|
```ruby
|
699
764
|
module EasyCrud
|
@@ -713,9 +778,9 @@ class LogTest
|
|
713
778
|
|
714
779
|
when we call `init` with arguments, we will call all on_init blocks defined in the role. In this example we inject attributes 'a' and 'b' with reader/writter and a predicate based on the name ex: `has_attr_a_or_not?`
|
715
780
|
|
716
|
-
### composable
|
781
|
+
### composable parameterized roles
|
717
782
|
|
718
|
-
To combine one or more
|
783
|
+
To combine one or more parameterized roles to another parameterized role you should do something like this:
|
719
784
|
|
720
785
|
```ruby
|
721
786
|
module Logabble2
|
@@ -889,7 +954,7 @@ ep.ping # will print "receive ping!"
|
|
889
954
|
ep.pong 1 # will print "receive pong with 1!"
|
890
955
|
```
|
891
956
|
|
892
|
-
Now, imagine what you can do with a
|
957
|
+
Now, imagine what you can do with a parameterized role: we can create all handles based on event names!
|
893
958
|
|
894
959
|
## IMPORTANT
|
895
960
|
|
@@ -899,12 +964,16 @@ Until the first 0.1 version I can change anything without warning.
|
|
899
964
|
|
900
965
|
I am open to suggestions too.
|
901
966
|
|
967
|
+
## Mailing List
|
968
|
+
|
969
|
+
https://groups.google.com/d/forum/moosex-ruby-dev-list
|
970
|
+
|
902
971
|
## TODO
|
903
972
|
|
904
|
-
1. Support to Roles ( it is a Module on Steroids )
|
905
|
-
2. Support to after/before/around
|
906
|
-
3. Improve the typecheck system (we should specify: we need an array of positive integers)
|
907
|
-
4. Improve the exception and warning system
|
973
|
+
1. Support to Roles ( it is a Module on Steroids ) [done]
|
974
|
+
2. Support to after/before/around [done]
|
975
|
+
3. Improve the typecheck system (we should specify: we need an array of positive integers) [done]
|
976
|
+
4. Improve the exception and warning system [in progress]
|
908
977
|
5. Profit!
|
909
978
|
|
910
979
|
## Limitations
|
@@ -920,3 +989,9 @@ Now has limited support to subclassing.
|
|
920
989
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
921
990
|
4. Push to the branch (`git push origin my-new-feature`)
|
922
991
|
5. Create new Pull Request
|
992
|
+
|
993
|
+
## About the Author
|
994
|
+
|
995
|
+
[@pac_man](https://twitter.com/pac_man)
|
996
|
+
|
997
|
+
[about.me](http://about.me/peczenyj)
|
data/lib/moosex.rb
CHANGED
@@ -7,410 +7,417 @@
|
|
7
7
|
#
|
8
8
|
require "moosex/version"
|
9
9
|
require "moosex/types"
|
10
|
+
require "weakref"
|
10
11
|
|
11
12
|
module MooseX
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
@@MOOSEX_WARNINGS = true
|
14
|
+
@@MOOSEX_FATAL = false
|
15
|
+
|
16
|
+
class FatalError < StandardError
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.warn(x, *c)
|
20
|
+
raise FatalError, "[MooseX] exception: #{x}",*c if @@MOOSEX_FATAL
|
20
21
|
Kernel.warn("[MooseX] warning: #{x}") if @@MOOSEX_WARNINGS
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.init(args={})
|
25
|
+
if args.has_key? :warnings
|
26
|
+
@@MOOSEX_WARNINGS = !! args[:warnings]
|
27
|
+
end
|
28
|
+
|
29
|
+
if args.has_key? :fatal
|
30
|
+
@@MOOSEX_FATAL = !! args[:fatal]
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
class RequiredMethodNotFoundError < NameError
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
def MooseX.included(c)
|
40
|
+
|
41
|
+
c.extend(MooseX::Core)
|
41
42
|
|
42
43
|
def c.init(*args)
|
43
44
|
__meta.roles.each{|role| role.call(*args)}
|
44
45
|
|
45
46
|
self
|
46
47
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
def initialize(*args)
|
77
|
-
if self.respond_to? :BUILDARGS
|
78
|
-
args = self.BUILDARGS(*args)
|
79
|
-
else
|
80
|
-
args = args[0]
|
81
|
-
end
|
82
|
-
|
83
|
-
self.class.__meta().init(self, args || {})
|
84
|
-
|
85
|
-
self.BUILD() if self.respond_to? :BUILD
|
86
|
-
end
|
87
|
-
|
88
|
-
def c.inherited(subclass)
|
89
|
-
subclass.class_exec do
|
90
|
-
old_meta = subclass.__meta
|
91
|
-
|
92
|
-
meta = MooseX::Meta.new(old_meta)
|
93
|
-
|
94
|
-
define_singleton_method(:__meta) { meta }
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
class Meta
|
101
|
-
attr_reader :attrs, :requires, :before, :after, :around, :roles
|
102
|
-
|
103
|
-
def initialize(old_meta=nil)
|
104
|
-
@initialized = false
|
105
|
-
@attrs = {}
|
106
|
-
@requires = []
|
107
|
-
@roles = []
|
108
|
-
@before = Hash.new { |hash, key| hash[key] = [] }
|
109
|
-
@after = Hash.new { |hash, key| hash[key] = [] }
|
110
|
-
@around = Hash.new { |hash, key| hash[key] = [] }
|
111
|
-
|
112
|
-
if old_meta
|
113
|
-
old_meta.attrs.each_pair do |key, value|
|
114
|
-
@attrs[key] = value.clone
|
115
|
-
end
|
116
|
-
@requires = old_meta.requires.clone
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def load_from(other_meta)
|
121
|
-
other_meta.attrs.each_pair do |key, value|
|
122
|
-
@attrs[key] = value.clone
|
123
|
-
end
|
124
|
-
@requires += other_meta.requires
|
125
|
-
end
|
126
|
-
|
127
|
-
def load_hooks(other_meta)
|
128
|
-
other_meta.before.each_pair do |m, b|
|
129
|
-
@before[m] += b.clone
|
130
|
-
end
|
131
|
-
other_meta.after.each_pair do |m, b|
|
132
|
-
@after[m] += b.clone
|
133
|
-
end
|
134
|
-
other_meta.around.each_pair do |m, b|
|
135
|
-
@around[m] += b.clone
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def add(attr)
|
140
|
-
@attrs[attr.attr_symbol] = attr
|
141
|
-
end
|
142
|
-
|
143
|
-
def add_requires(method)
|
144
|
-
@requires << method
|
145
|
-
end
|
146
|
-
|
147
|
-
def add_before(method_name, block)
|
148
|
-
@before[method_name] << block.clone
|
149
|
-
end
|
150
|
-
|
151
|
-
def add_after(method_name, block)
|
152
|
-
@after[method_name] << block.clone
|
153
|
-
end
|
154
|
-
|
155
|
-
def add_around(method_name, block)
|
156
|
-
@around[method_name] << block.clone
|
157
|
-
end
|
158
|
-
|
159
|
-
def add_role(block)
|
160
|
-
@roles << block
|
48
|
+
|
49
|
+
def c.included(x)
|
50
|
+
|
51
|
+
MooseX.included(x)
|
52
|
+
x.__meta.load_from(self.__meta)
|
53
|
+
|
54
|
+
return unless x.is_a? Class
|
55
|
+
|
56
|
+
x.__meta.load_hooks(self.__meta)
|
57
|
+
self.__meta.init_klass(x)
|
58
|
+
|
59
|
+
x.__meta.requires.each do |method|
|
60
|
+
unless x.public_instance_methods.include? method
|
61
|
+
MooseX.warn "you must implement method '#{method}' in #{x} #{x.class}: required"# if $MOOSEX_DEBUG
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
meta = MooseX::Meta.new
|
67
|
+
|
68
|
+
unless c.respond_to? :__meta
|
69
|
+
c.class_exec do
|
70
|
+
define_singleton_method(:__meta) { meta }
|
71
|
+
define_singleton_method(:__meta_define_method) do |method_name, &proc|
|
72
|
+
define_method(method_name, proc)
|
73
|
+
end
|
74
|
+
end
|
161
75
|
end
|
76
|
+
|
77
|
+
def initialize(*args)
|
78
|
+
if self.respond_to? :BUILDARGS
|
79
|
+
args = self.BUILDARGS(*args)
|
80
|
+
else
|
81
|
+
args = args[0]
|
82
|
+
end
|
83
|
+
|
84
|
+
self.class.__meta().init(self, args || {})
|
85
|
+
|
86
|
+
self.BUILD() if self.respond_to? :BUILD
|
87
|
+
end
|
88
|
+
|
89
|
+
def c.inherited(subclass)
|
90
|
+
subclass.class_exec do
|
91
|
+
old_meta = subclass.__meta
|
162
92
|
|
163
|
-
|
164
|
-
#return if @initialized
|
93
|
+
meta = MooseX::Meta.new(old_meta)
|
165
94
|
|
166
|
-
|
167
|
-
|
168
|
-
|
95
|
+
define_singleton_method(:__meta) { meta }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
class Meta
|
102
|
+
attr_reader :attrs, :requires, :before, :after, :around, :roles
|
103
|
+
|
104
|
+
def initialize(old_meta=nil)
|
105
|
+
@initialized = false
|
106
|
+
@attrs = {}
|
107
|
+
@requires = []
|
108
|
+
@roles = []
|
109
|
+
@before = Hash.new { |hash, key| hash[key] = [] }
|
110
|
+
@after = Hash.new { |hash, key| hash[key] = [] }
|
111
|
+
@around = Hash.new { |hash, key| hash[key] = [] }
|
112
|
+
|
113
|
+
if old_meta
|
114
|
+
old_meta.attrs.each_pair do |key, value|
|
115
|
+
@attrs[key] = value.clone
|
116
|
+
end
|
117
|
+
@requires = old_meta.requires.clone
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def load_from(other_meta)
|
122
|
+
other_meta.attrs.each_pair do |key, value|
|
123
|
+
@attrs[key] = value.clone
|
124
|
+
end
|
125
|
+
@requires += other_meta.requires
|
126
|
+
end
|
127
|
+
|
128
|
+
def load_hooks(other_meta)
|
129
|
+
other_meta.before.each_pair do |m, b|
|
130
|
+
@before[m] += b.clone
|
131
|
+
end
|
132
|
+
other_meta.after.each_pair do |m, b|
|
133
|
+
@after[m] += b.clone
|
134
|
+
end
|
135
|
+
other_meta.around.each_pair do |m, b|
|
136
|
+
@around[m] += b.clone
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def add(attr)
|
141
|
+
if @attrs.has_key?(attr.attr_symbol) && ! attr.override
|
142
|
+
raise FatalError, "#{attr.attr_symbol} already exists, you should specify override: true"
|
143
|
+
end
|
144
|
+
@attrs[attr.attr_symbol] = attr
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_requires(method)
|
148
|
+
@requires << method
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_before(method_name, block)
|
152
|
+
@before[method_name] << block.clone
|
153
|
+
end
|
154
|
+
|
155
|
+
def add_after(method_name, block)
|
156
|
+
@after[method_name] << block.clone
|
157
|
+
end
|
158
|
+
|
159
|
+
def add_around(method_name, block)
|
160
|
+
@around[method_name] << block.clone
|
161
|
+
end
|
162
|
+
|
163
|
+
def add_role(block)
|
164
|
+
@roles << block
|
165
|
+
end
|
166
|
+
|
167
|
+
def init_klass(klass)
|
168
|
+
#return if @initialized
|
169
|
+
|
170
|
+
[@before.keys + @after.keys + @around.keys].flatten.uniq.each do |method_name|
|
171
|
+
begin
|
172
|
+
method = klass.instance_method method_name
|
169
173
|
rescue => e
|
170
174
|
MooseX.warn "Unable to apply hooks (after/before/around) in #{klass}::#{method_name} : #{e}" # if $MOOSEX_DEBUG
|
171
175
|
next
|
172
176
|
end
|
173
177
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
178
|
+
before = @before[method_name]
|
179
|
+
after = @after[method_name]
|
180
|
+
around = @around[method_name]
|
181
|
+
|
182
|
+
klass.__meta_define_method(method_name) do |*args, &proc|
|
183
|
+
before.each{|b| b.call(self,*args, &proc)}
|
184
|
+
|
185
|
+
original = lambda do |object, *args, &proc|
|
186
|
+
method.bind(object).call(*args, &proc)
|
187
|
+
end
|
188
|
+
|
189
|
+
result = around.inject(original) do |lambda1, lambda2|
|
190
|
+
lambda2.curry[lambda1]
|
191
|
+
end.call(self, *args, &proc)
|
192
|
+
|
193
|
+
after.each{|b| b.call(self,*args, &proc)}
|
194
|
+
|
195
|
+
result
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def init(object, args)
|
201
|
+
@attrs.each_pair{ |symbol, attr| attr.init(object, args) }
|
202
|
+
|
203
|
+
MooseX.warn "unused attributes #{args} for #{object.class}", caller unless args.empty?
|
204
|
+
|
205
|
+
@requires.each do |method|
|
206
|
+
unless object.respond_to? method
|
207
|
+
raise RequiredMethodNotFoundError,
|
208
|
+
"you must implement method '#{method}' in #{object.class}: required"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
module Core
|
215
|
+
def on_init(&block)
|
216
|
+
__meta.add_role(block)
|
217
|
+
end
|
218
|
+
|
219
|
+
def after(*methods_name, &block)
|
220
|
+
methods_name.each do |method_name|
|
221
|
+
begin
|
222
|
+
method = instance_method method_name
|
223
|
+
|
224
|
+
define_method method_name do |*args, &proc|
|
225
|
+
result = method.bind(self).call(*args, &proc)
|
226
|
+
block.call(self,*args,&proc)
|
227
|
+
result
|
228
|
+
end
|
229
|
+
rescue => e
|
230
|
+
MooseX.warn "unable to apply hook after in #{method_name} @ #{self}: #{e}", caller() if self.is_a?(Class)
|
231
|
+
__meta.add_after(method_name, block)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def before(*methods_name, &block)
|
237
|
+
methods_name.each do |method_name|
|
238
|
+
begin
|
239
|
+
method = instance_method method_name
|
240
|
+
|
241
|
+
define_method method_name do |*args, &proc|
|
242
|
+
block.call(self,*args, &proc)
|
243
|
+
method.bind(self).call(*args, &proc)
|
244
|
+
end
|
245
|
+
rescue => e
|
246
|
+
MooseX.warn "unable to apply hook before in #{method_name} @ #{self}: #{e}", caller() if self.is_a?(Class)
|
247
|
+
__meta.add_before(method_name, block)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def around(*methods_name, &block)
|
253
|
+
methods_name.each do |method_name|
|
254
|
+
begin
|
255
|
+
|
256
|
+
method = instance_method method_name
|
257
|
+
|
258
|
+
code = Proc.new do | o, *a, &proc|
|
259
|
+
method.bind(o).call(*a,&proc)
|
260
|
+
end
|
261
|
+
|
262
|
+
define_method method_name do |*args, &proc|
|
263
|
+
block.call(code, self,*args, &proc)
|
264
|
+
end
|
265
|
+
|
266
|
+
rescue => e
|
267
|
+
MooseX.warn "unable to apply hook around in #{method_name} @ #{self}: #{e}", caller() if self.is_a?(Class)
|
268
|
+
__meta.add_around(method_name, block)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def requires(*methods)
|
274
|
+
|
275
|
+
methods.each do |method_name|
|
276
|
+
__meta.add_requires(method_name)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def has(attr_name, attr_options = {})
|
281
|
+
if attr_name.is_a? Array
|
282
|
+
attr_name.each do |attr|
|
283
|
+
has(attr, attr_options)
|
284
|
+
end
|
285
|
+
elsif attr_name.is_a? Hash
|
286
|
+
attr_name.each_pair do |attr, options |
|
287
|
+
has(attr, options)
|
288
|
+
end
|
289
|
+
else
|
290
|
+
attr = MooseX::Attribute.new(attr_name, attr_options, self)
|
291
|
+
|
292
|
+
__meta.add(attr)
|
293
|
+
|
294
|
+
attr.methods.each_pair do |method, proc|
|
295
|
+
define_method method, &proc
|
296
|
+
end
|
297
|
+
|
298
|
+
if attr.is.eql?(:rwp)
|
299
|
+
private attr.writter
|
300
|
+
elsif attr.is.eql?(:private)
|
301
|
+
private attr.writter
|
302
|
+
private attr.reader
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
class InvalidAttributeError < TypeError
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
class Attribute
|
313
|
+
include MooseX::Types
|
314
|
+
|
315
|
+
attr_reader :attr_symbol, :is, :reader, :writter, :lazy, :builder, :methods, :override
|
316
|
+
DEFAULTS= {
|
317
|
+
is: :rw,
|
318
|
+
weak: false,
|
319
|
+
lazy: false,
|
320
|
+
clearer: false,
|
321
|
+
required: false,
|
322
|
+
predicate: false,
|
323
|
+
isa: isAny,
|
324
|
+
handles: {},
|
325
|
+
trigger: lambda {|object,value|}, # TODO: implement
|
326
|
+
coerce: lambda {|object| object}, # TODO: implement
|
327
|
+
doc: nil,
|
328
|
+
override: false,
|
329
|
+
}
|
330
|
+
|
331
|
+
REQUIRED = []
|
332
|
+
|
333
|
+
VALIDATE = {
|
334
|
+
is: lambda do |is, field_name|
|
335
|
+
unless [:rw, :rwp, :ro, :lazy, :private].include?(is)
|
336
|
+
raise InvalidAttributeError, "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy"
|
337
|
+
end
|
338
|
+
end,
|
339
|
+
};
|
340
|
+
|
341
|
+
COERCE = {
|
342
|
+
is: lambda do |is, field_name|
|
343
|
+
is.to_sym
|
344
|
+
end,
|
345
|
+
isa: lambda do |isa, field_name|
|
346
|
+
isType(isa)
|
347
|
+
end,
|
348
|
+
default: lambda do |default, field_name|
|
349
|
+
return default if default.is_a? Proc
|
350
|
+
|
351
|
+
return lambda { default }
|
352
|
+
end,
|
353
|
+
required: lambda do |required, field_name|
|
354
|
+
!!required
|
355
|
+
end,
|
356
|
+
lazy: lambda do |lazy, field_name|
|
357
|
+
!!lazy
|
358
|
+
end,
|
359
|
+
predicate: lambda do |predicate, field_name|
|
360
|
+
if ! predicate
|
361
|
+
return false
|
362
|
+
elsif predicate.is_a? TrueClass
|
363
|
+
return "has_#{field_name}?".to_sym
|
364
|
+
end
|
365
|
+
|
366
|
+
begin
|
367
|
+
predicate.to_sym
|
368
|
+
rescue => e
|
369
|
+
# create a nested exception here
|
370
|
+
raise InvalidAttributeError, "cannot coerce field predicate to a symbol for #{field_name}: #{e}"
|
371
|
+
end
|
372
|
+
end,
|
373
|
+
clearer: lambda do|clearer, field_name|
|
374
|
+
if ! clearer
|
375
|
+
return false
|
376
|
+
elsif clearer.is_a? TrueClass
|
377
|
+
return "clear_#{field_name}!".to_sym
|
378
|
+
end
|
379
|
+
|
380
|
+
begin
|
381
|
+
clearer.to_sym
|
382
|
+
rescue => e
|
383
|
+
# create a nested exception here
|
384
|
+
raise InvalidAttributeError, "cannot coerce field clearer to a symbol for #{field_name}: #{e}"
|
385
|
+
end
|
386
|
+
end,
|
387
|
+
handles: lambda do |handles, field_name|
|
388
|
+
|
389
|
+
unless handles.is_a? Hash
|
390
|
+
|
391
|
+
array_of_handles = handles
|
392
|
+
|
393
|
+
unless array_of_handles.is_a? Array
|
394
|
+
array_of_handles = [ array_of_handles ]
|
395
|
+
end
|
396
|
+
|
397
|
+
handles = array_of_handles.map do |handle|
|
398
|
+
|
399
|
+
if handle == BasicObject
|
400
|
+
|
401
|
+
raise InvalidAttributeError, "ops, should not use BasicObject for handles in #{field_name}"
|
402
|
+
|
403
|
+
elsif handle.is_a? Class
|
404
|
+
|
405
|
+
handle = handle.public_instance_methods - handle.superclass.public_instance_methods
|
406
|
+
|
407
|
+
elsif handle.is_a? Module
|
408
|
+
|
409
|
+
handle = handle.public_instance_methods
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
handle
|
414
|
+
|
415
|
+
end.flatten.reduce({}) do |hash, method_name|
|
416
|
+
hash.merge({ method_name => method_name })
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
handles.map do |key,value|
|
414
421
|
if value.is_a? Hash
|
415
422
|
raise "ops! Handle should accept only one map / currying" unless value.count == 1
|
416
423
|
|
@@ -418,233 +425,252 @@ module MooseX
|
|
418
425
|
|
419
426
|
{ key.to_sym => [original.to_sym, currying] }
|
420
427
|
else
|
421
|
-
|
428
|
+
{ key.to_sym => value.to_sym }
|
429
|
+
end
|
430
|
+
end.reduce({}) do |hash,e|
|
431
|
+
hash.merge(e)
|
432
|
+
end
|
433
|
+
end,
|
434
|
+
reader: lambda do |reader, field_name|
|
435
|
+
reader.to_sym
|
436
|
+
end,
|
437
|
+
writter: lambda do |writter, field_name|
|
438
|
+
writter.to_sym
|
439
|
+
end,
|
440
|
+
builder: lambda do |builder, field_name|
|
441
|
+
unless builder.is_a? Proc
|
442
|
+
builder_method_name = builder.to_sym
|
443
|
+
builder = lambda do |object|
|
444
|
+
object.send(builder_method_name)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
builder
|
449
|
+
end,
|
450
|
+
init_arg: lambda do |init_arg, field_name|
|
451
|
+
init_arg.to_sym
|
452
|
+
end,
|
453
|
+
trigger: lambda do |trigger, field_name|
|
454
|
+
unless trigger.is_a? Proc
|
455
|
+
trigger_method_name = trigger.to_sym
|
456
|
+
trigger = lambda do |object, value|
|
457
|
+
object.send(trigger_method_name,value)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
trigger
|
462
|
+
end,
|
463
|
+
coerce: lambda do |coerce, field_name|
|
464
|
+
unless coerce.is_a? Proc
|
465
|
+
coerce_method_name = coerce.to_sym
|
466
|
+
coerce = lambda do |object|
|
467
|
+
object.send(coerce_method_name)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
coerce
|
472
|
+
end,
|
473
|
+
weak: lambda do |weak, field_name|
|
474
|
+
!! weak
|
475
|
+
end,
|
476
|
+
doc: lambda do |doc, field_name|
|
477
|
+
doc.to_s
|
478
|
+
end,
|
479
|
+
override: lambda do |override, field_name|
|
480
|
+
!! override
|
481
|
+
end,
|
482
|
+
};
|
483
|
+
|
484
|
+
def initialize(a, o ,x)
|
485
|
+
#o ||= {}
|
486
|
+
# todo extract this to a framework, see issue #21 on facebook
|
487
|
+
o = DEFAULTS.merge({
|
488
|
+
reader: a,
|
489
|
+
writter: a.to_s.concat("=").to_sym,
|
490
|
+
builder: "build_#{a}".to_sym,
|
491
|
+
init_arg: a,
|
492
|
+
}).merge(o)
|
493
|
+
|
494
|
+
REQUIRED.each { |field|
|
495
|
+
unless o.has_key?(field)
|
496
|
+
raise InvalidAttributeError, "field #{field} is required for Attribute #{a}"
|
497
|
+
end
|
498
|
+
}
|
499
|
+
COERCE.each_pair do |field, coerce|
|
500
|
+
if o.has_key? field
|
501
|
+
o[field] = coerce.call(o[field], a)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
VALIDATE.each_pair do |field, validate|
|
505
|
+
return if ! o.has_key? field
|
506
|
+
|
507
|
+
validate.call(o[field], a)
|
508
|
+
end
|
509
|
+
|
510
|
+
if o[:is].eql? :ro
|
511
|
+
o[:writter] = nil
|
512
|
+
elsif o[:is].eql? :lazy
|
513
|
+
o[:lazy] = true
|
514
|
+
o[:writter] = nil
|
515
|
+
end
|
516
|
+
|
517
|
+
unless o[:lazy]
|
518
|
+
o[:builder] = nil
|
519
|
+
end
|
520
|
+
|
521
|
+
if o[:weak]
|
522
|
+
old_coerce = o[:coerce]
|
523
|
+
o[:coerce] = lambda do |value|
|
524
|
+
WeakRef.new old_coerce.call(value)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
@attr_symbol = a
|
529
|
+
@is = o.delete(:is)
|
530
|
+
@isa = o.delete(:isa)
|
531
|
+
@default = o.delete(:default)
|
532
|
+
@required = o.delete(:required)
|
533
|
+
@predicate = o.delete(:predicate)
|
534
|
+
@clearer = o.delete(:clearer)
|
535
|
+
@handles = o.delete(:handles)
|
536
|
+
@lazy = o.delete(:lazy)
|
537
|
+
@reader = o.delete(:reader)
|
538
|
+
@writter = o.delete(:writter)
|
539
|
+
@builder = o.delete(:builder)
|
540
|
+
@init_arg = o.delete(:init_arg)
|
541
|
+
@trigger = o.delete(:trigger)
|
542
|
+
@coerce = o.delete(:coerce)
|
543
|
+
@weak = o.delete(:weak)
|
544
|
+
@documentation = o.delete(:doc)
|
545
|
+
@override = o.delete(:override)
|
546
|
+
@methods = {}
|
547
|
+
|
548
|
+
MooseX.warn "Unused attributes #{o} for attribute #{a} @ #{x} #{x.class}",caller() if ! o.empty?
|
549
|
+
|
550
|
+
if @reader
|
551
|
+
@methods[@reader] = generate_reader
|
552
|
+
end
|
553
|
+
|
554
|
+
if @writter
|
555
|
+
@methods[@writter] = generate_writter
|
556
|
+
end
|
557
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
558
|
+
if @predicate
|
559
|
+
@methods[@predicate] = Proc.new do
|
560
|
+
instance_variable_defined? inst_variable_name
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
if @clearer
|
565
|
+
@methods[@clearer] = Proc.new do
|
566
|
+
if instance_variable_defined? inst_variable_name
|
567
|
+
remove_instance_variable inst_variable_name
|
422
568
|
end
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
inst_variable_name = "@#{@attr_symbol}".to_sym
|
532
|
-
if @predicate
|
533
|
-
@methods[@predicate] = Proc.new do
|
534
|
-
instance_variable_defined? inst_variable_name
|
535
|
-
end
|
536
|
-
end
|
537
|
-
|
538
|
-
if @clearer
|
539
|
-
@methods[@clearer] = Proc.new do
|
540
|
-
if instance_variable_defined? inst_variable_name
|
541
|
-
remove_instance_variable inst_variable_name
|
542
|
-
end
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
attr_symbol = @attr_symbol
|
547
|
-
@handles.each_pair do | method, target_method |
|
548
|
-
if target_method.is_a? Array
|
549
|
-
original, currying = target_method
|
550
|
-
|
551
|
-
@methods[method] = Proc.new do |*args, &proc|
|
552
|
-
|
553
|
-
a1 = [ currying ]
|
554
|
-
|
555
|
-
if currying.is_a?Proc
|
556
|
-
a1 = currying.call()
|
557
|
-
elsif currying.is_a? Array
|
558
|
-
a1 = currying.map{|c| (c.is_a?(Proc)) ? c.call : c }
|
559
|
-
end
|
560
|
-
|
561
|
-
self.send(attr_symbol).send(original, *a1, *args, &proc)
|
562
|
-
end
|
563
|
-
else
|
564
|
-
@methods[method] = Proc.new do |*args, &proc|
|
565
|
-
self.send(attr_symbol).send(target_method, *args, &proc)
|
566
|
-
end
|
567
|
-
end
|
568
|
-
end
|
569
|
-
end
|
570
|
-
|
571
|
-
def init(object, args)
|
572
|
-
value = nil
|
573
|
-
value_from_default = false
|
574
|
-
|
575
|
-
if args.has_key? @init_arg
|
576
|
-
value = args.delete(@init_arg)
|
577
|
-
elsif @default
|
578
|
-
value = @default.call
|
579
|
-
value_from_default = true
|
580
|
-
elsif @required
|
581
|
-
raise InvalidAttributeError, "attr \"#{@attr_symbol}\" is required"
|
582
|
-
else
|
583
|
-
return
|
584
|
-
end
|
585
|
-
|
586
|
-
value = @coerce.call(value)
|
587
|
-
begin
|
588
|
-
@isa.call( value )
|
589
|
-
rescue MooseX::Types::TypeCheckError => e
|
590
|
-
raise MooseX::Types::TypeCheckError, "isa check for field #{attr_symbol}: #{e}"
|
591
|
-
end
|
592
|
-
unless value_from_default
|
593
|
-
@trigger.call(object, value)
|
594
|
-
end
|
595
|
-
inst_variable_name = "@#{@attr_symbol}".to_sym
|
596
|
-
object.instance_variable_set inst_variable_name, value
|
597
|
-
end
|
598
|
-
|
599
|
-
private
|
600
|
-
def generate_reader
|
601
|
-
inst_variable_name = "@#{@attr_symbol}".to_sym
|
602
|
-
|
603
|
-
builder = @builder
|
604
|
-
before_get = lambda {|object| }
|
605
|
-
|
606
|
-
if @lazy
|
607
|
-
type_check = @isa
|
608
|
-
coerce = @coerce
|
609
|
-
trigger = @trigger
|
610
|
-
before_get = lambda do |object|
|
611
|
-
return if object.instance_variable_defined? inst_variable_name
|
612
|
-
|
613
|
-
value = builder.call(object)
|
614
|
-
value = coerce.call(value)
|
615
|
-
begin
|
616
|
-
type_check.call( value )
|
617
|
-
rescue MooseX::Types::TypeCheckError => e
|
618
|
-
raise MooseX::Types::TypeCheckError, "isa check for #{inst_variable_name} from builder: #{e}"
|
619
|
-
end
|
620
|
-
|
621
|
-
trigger.call(object, value)
|
622
|
-
object.instance_variable_set(inst_variable_name, value)
|
623
|
-
end
|
624
|
-
end
|
625
|
-
|
626
|
-
Proc.new do
|
627
|
-
before_get.call(self)
|
628
|
-
instance_variable_get inst_variable_name
|
629
|
-
end
|
630
|
-
end
|
631
|
-
|
632
|
-
def generate_writter
|
633
|
-
writter_name = @writter
|
634
|
-
inst_variable_name = "@#{@attr_symbol}".to_sym
|
635
|
-
coerce = @coerce
|
636
|
-
type_check = @isa
|
637
|
-
trigger = @trigger
|
638
|
-
Proc.new do |value|
|
639
|
-
value = coerce.call(value)
|
640
|
-
begin
|
641
|
-
type_check.call( value )
|
642
|
-
rescue MooseX::Types::TypeCheckError => e
|
643
|
-
raise MooseX::Types::TypeCheckError, "isa check for #{writter_name}: #{e}"
|
644
|
-
end
|
645
|
-
trigger.call(self,value)
|
646
|
-
instance_variable_set inst_variable_name, value
|
647
|
-
end
|
648
|
-
end
|
649
|
-
end
|
650
|
-
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
attr_symbol = @attr_symbol
|
573
|
+
@handles.each_pair do | method, target_method |
|
574
|
+
if target_method.is_a? Array
|
575
|
+
original, currying = target_method
|
576
|
+
|
577
|
+
@methods[method] = Proc.new do |*args, &proc|
|
578
|
+
|
579
|
+
a1 = [ currying ]
|
580
|
+
|
581
|
+
if currying.is_a?Proc
|
582
|
+
a1 = currying.call()
|
583
|
+
elsif currying.is_a? Array
|
584
|
+
a1 = currying.map{|c| (c.is_a?(Proc)) ? c.call : c }
|
585
|
+
end
|
586
|
+
|
587
|
+
self.send(attr_symbol).send(original, *a1, *args, &proc)
|
588
|
+
end
|
589
|
+
else
|
590
|
+
@methods[method] = Proc.new do |*args, &proc|
|
591
|
+
self.send(attr_symbol).send(target_method, *args, &proc)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
def init(object, args)
|
598
|
+
value = nil
|
599
|
+
value_from_default = false
|
600
|
+
|
601
|
+
if args.has_key? @init_arg
|
602
|
+
value = args.delete(@init_arg)
|
603
|
+
elsif @default
|
604
|
+
value = @default.call
|
605
|
+
value_from_default = true
|
606
|
+
elsif @required
|
607
|
+
raise InvalidAttributeError, "attr \"#{@attr_symbol}\" is required"
|
608
|
+
else
|
609
|
+
return
|
610
|
+
end
|
611
|
+
|
612
|
+
value = @coerce.call(value)
|
613
|
+
begin
|
614
|
+
@isa.call( value )
|
615
|
+
rescue MooseX::Types::TypeCheckError => e
|
616
|
+
raise MooseX::Types::TypeCheckError, "isa check for field #{attr_symbol}: #{e}"
|
617
|
+
end
|
618
|
+
unless value_from_default
|
619
|
+
@trigger.call(object, value)
|
620
|
+
end
|
621
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
622
|
+
object.instance_variable_set inst_variable_name, value
|
623
|
+
end
|
624
|
+
|
625
|
+
private
|
626
|
+
def generate_reader
|
627
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
628
|
+
|
629
|
+
builder = @builder
|
630
|
+
before_get = lambda {|object| }
|
631
|
+
|
632
|
+
if @lazy
|
633
|
+
type_check = @isa
|
634
|
+
coerce = @coerce
|
635
|
+
trigger = @trigger
|
636
|
+
before_get = lambda do |object|
|
637
|
+
return if object.instance_variable_defined? inst_variable_name
|
638
|
+
|
639
|
+
value = builder.call(object)
|
640
|
+
value = coerce.call(value)
|
641
|
+
begin
|
642
|
+
type_check.call( value )
|
643
|
+
rescue MooseX::Types::TypeCheckError => e
|
644
|
+
raise MooseX::Types::TypeCheckError, "isa check for #{inst_variable_name} from builder: #{e}"
|
645
|
+
end
|
646
|
+
|
647
|
+
trigger.call(object, value)
|
648
|
+
object.instance_variable_set(inst_variable_name, value)
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
Proc.new do
|
653
|
+
before_get.call(self)
|
654
|
+
instance_variable_get inst_variable_name
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
def generate_writter
|
659
|
+
writter_name = @writter
|
660
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
661
|
+
coerce = @coerce
|
662
|
+
type_check = @isa
|
663
|
+
trigger = @trigger
|
664
|
+
Proc.new do |value|
|
665
|
+
value = coerce.call(value)
|
666
|
+
begin
|
667
|
+
type_check.call( value )
|
668
|
+
rescue MooseX::Types::TypeCheckError => e
|
669
|
+
raise MooseX::Types::TypeCheckError, "isa check for #{writter_name}: #{e}"
|
670
|
+
end
|
671
|
+
trigger.call(self,value)
|
672
|
+
instance_variable_set inst_variable_name, value
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
end
|