README.md in moosex-0.0.18 vs README.md in moosex-0.0.19
- old
+ new
@@ -28,11 +28,11 @@
- [MooX::Types::MooseLike::Base](http://search.cpan.org/~mateu/MooX-Types-MooseLike-0.25/lib/MooX/Types/MooseLike/Base.pm)
- [MooseX::Event](http://search.cpan.org/~winter/MooseX-Event-v0.2.0/lib/MooseX/Event.pm)
- [MooseX::Role::Parameterized](http://search.cpan.org/~sartak/MooseX-Role-Parameterized-1.02/lib/MooseX/Role/Parameterized/Tutorial.pod)
See also:
-
+- [Reindeer](https://github.com/broquaint/reindeer), another Moose port to Ruby (still on 0.0.1 version)
- [Joose](https://code.google.com/p/joose-js/), a javascript port of Moose.
- [Perl 6](http://en.wikipedia.org/wiki/Perl_6#Object-oriented_programming) Perl 6 OO programming style.
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' :/
@@ -53,11 +53,11 @@
}
has y: {
is: :rw,
isa: Integer,
- default: lambda { 0 }, # you should specify a lambda
+ default: -> { 0 }, # you should specify a lambda
}
def clear!
self.x= 0 # to run with type-check you must
self.y= 0 # use the setter instad @x=
@@ -129,10 +129,13 @@
has [:bar, :baz, :bam], {
is: :rw,
isa: Integer,
default: 0
}
+
+ # you can declare inline too
+ has :another, is: :rw, isa: Integer, default: ->{ Object.new }
end
```
## DSL: the 'has' method
@@ -164,12 +167,12 @@
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.
You can specify your own kind of type validation.
```ruby
- isa: lambda do |new_value|
- unless new_value.respond_to? :to_sym
+ isa: ->(value) do
+ unless value.respond_to? :to_sym
raise "bar should respond to to_sym method!"
end
end,
```
@@ -182,11 +185,11 @@
```ruby
default: 0,
```
or
```ruby
- default: lambda{ MyObject.new },
+ default: -> { MyObject.new },
```
### required => true|false
if true, the constructor will raise error if this attribute was not present.
@@ -202,11 +205,11 @@
### coerce => method name|lambda
You can try to coerce the attribute value by a lambda/method before the type check phase. For example you can do
```ruby
- coerce: lambda{ |new_value| new_value.to_i },
+ coerce: ->(value) { value.to_i },
```
or just
```ruby
@@ -237,10 +240,12 @@
my_method_1: :method1,
my_method_2: :method2,
},
```
+handles is similar to [Forwardable](http://ruby-doc.org/stdlib-2.1.0/libdoc/forwardable/rdoc/Forwardable.html) module, the difference is the currying support and it is integrate with the has method/endpoint. If you want to use Forwardable, please use the reader method name instead the attribute name with @.
+
Optional.
#### Currying
It is possible curry constant values declaring a pair/hash and set one or more constant values / lambdas
@@ -262,11 +267,11 @@
are equivalent. You can curry as many arguments as you can.
```ruby
handles: {
my_method_2: {
- method2: [1, lambda{ 2 } ]
+ method2: [1, ->{ 2 } ]
}
},
```
will generate
@@ -301,12 +306,12 @@
### trigger => method name|lambda
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.
```ruby
- trigger: lambda do |object, new_value|
- object.logger.log "change the attribute value to #{new_value}"
+ trigger: ->(this, new_value) do
+ this.logger.log "change the attribute value to #{new_value}"
end
```
or
```ruby
has a: { is: :rw }
@@ -443,14 +448,17 @@
include MooseX
has x: {
is: :rw,
lazy: :true,
- builder: lambda{ |foo| Some::Class.new } # you can ignore foo, or use it!
+ builder: ->(this) { Some::Class.new } # you can ignore foo, or use it!
}
end
```
+
+The difference between builder and default is: default is for initialization if you omit the value in the constructor and you can't access the object (it is not "created" yet), builder is for lazy attributes, when you call the reader method for the first time ( or after clear it ) we will initialize the attribute and you have access to the object (in this example using the parameter 'this' in the lambda or using a normal method).
+
Optional.
### weak => true|false
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.
@@ -470,20 +478,175 @@
You should verify with `weakref_alive?` method to avoid exceptions.
Optional.
-## doc => String
+### doc => String
You can add a string metadata about the attribute. If you include MooseX with `meta: true` you can inspect the list of attributes and documentation.
Optional.
-## override => true|false
+### override => true|false
If you need override one attribute, you should use `override: true`, or MooseX will raise one exception.
+### traits => Trait|[Array of Traits]
+
+The objective of use traits is extends the original attribute, using delegators. We support few traits at this moment and, **Important**, if you set a list of Traits we will apply each trait in sequence. Have a Suggestion? Open an Issue on Github!
+
+#### Trait Counter
+
+```ruby
+class MyHomePage
+ include MooseX::Types
+
+ has :counter, {
+ is: :ro,
+ isa: Integer,
+ default: 0,
+ traits: MooseX::Traits::Counter,
+ handles: {
+ increase_counter: :inc,
+ decrease_counter: :dec,
+ reset_counter_to_zero!: :reset,
+ }
+ }
+```
+
+In this example, the class MyHomePage has one attribute counter, Integer, and you can increase and decrease the counter value (or reset to zero) using `increase_counter`, `decrease_counter` and `reset_counter_to_zero!` methods. Without the trait `Counter` you should create this three methods and set the visibility to `:rwp` to be able to access the value, do some math and save. In this example, `MooseX::Traits::Counter` is a SimpleDelegator who act as a wrapper to the original value and act as a proxy for all methods except `inc`, `dec` and `reset`.
+
+We apply the traits list on default values, constructor and writter, after type check and coerce, and it is useful to extend the behavior of the attribute. In this case, 0 is a Immutable object, we can't increase the value using some method (different than String, when you use methods like `capitalize!`) but we can use a Delegator to act as a proxy and increase/decrease the value.
+
+#### Trait Bool
+
+Another example, using mutable boolean values:
+
+```ruby
+ has bit: {
+ is: :ro,
+ default: true,
+ traits: MooseX::Traits::Bool,
+ handles: [ :toggle!, :value ],
+ }
+```
+
+In this case, we have a bit, and this bit is turn on (true). It is read-only so you can't set the bit. But using `toggle!` we can turn true => false and false => true. To access the original boolean value we should use !! ( to coerce to a true|false value ) or we can access the `value` method. It is mandatory if you want to use in if/unless statements. Of course you can change the name of each method.
+
+#### Trait Pair
+
+Sometimes we need store an array of fixed size and each element has an identity. For example, we should store arrays as tuples, or pairs. Of course we can create an object to be more clear, but we have this option and, sometimes, it is useful. Now, consider this example:
+
+```ruby
+ has array_with_surname_name: {
+ is: :private,
+ isa: Array, # or isTuple(String, String) if you include MooseX::Types
+ traits: [ MooseX::Traits::Pair ],
+ handles: {
+ :surname => :first,
+ :surname= => :first=,
+ :name => :second,
+ :name= => :second=,
+ :surname_and_name => { join: ->{","} }
+ },
+ required: true,
+ }
+```
+
+We store surname and name as a tuple of Strings. Instead access `array_with_surname_name[0]` or `array_with_surname_name[1]`, we can apply the Trait `Pair`, and access (or save!) each component of this pair, but you can't change the pair itself. Look the example of currying in `surname_and_name`, calling join with "," as an argument.
+
+#### Trait RescueToNil
+
+The objective of `MooseX::Traits::RescueToNil` is avoid `NoMethodError` if, for example, you set nil as value. For example:
+
+```ruby
+ has important: {
+ is: :rw,
+ default: 0,
+ traits: MooseX::Traits::RescueToNil,
+ handles: {
+ plus: :+,
+ minus: :-,
+ }
+ }
+```
+Imagine you can accept nil as a valid value. In this case, you can't use `+` or `-`, right? It will raise a NoMethodError. Well... you can avoid this with RescueToNil trait. Using this, we will return `nil` for each operation in case of NoMethodError, and raise other kinds of exceptions.
+
+#### Trait RescueToZero
+
+Similar to RescueToNil, but return 0 in case of `NoMethodError`.
+
+#### Trait RescueToEmptyString
+
+Similar to RescueToNil, but return empty string "" in case of `NoMethodError`.
+
+##### Create your own trait
+
+You should create a Class with a constructor who will receive a reference to the value. For example, the trait Counter is using SimpleDelegator:
+
+```ruby
+require 'delegate' # to use SimpleDelegator
+
+module MooseX
+ module Traits
+ class Counter < SimpleDelegator
+ def initialize(value)
+ @value = value
+ super(@value)
+ end
+
+ def inc(by=1)
+ @value += by
+ __setobj__(@value)
+ @value
+ end
+...
+```
+You can create or extend your own Traits too. It is easy.
+
+##### Composable Traits
+
+It is easy compose traits, for example:
+
+```ruby
+class ComplexExample
+ include MooseX
+ include MooseX::Types
+
+ has surname_name: {
+ is: :rw,
+ isa: isMaybe(isTuple(String, String)),
+ default: nil,
+ traits: [ MooseX::Traits::RescueToEmptyString, MooseX::Traits::Pair ],
+ handles: {
+ surname: :first,
+ name: :second,
+ :surname= => :first=,
+ :name= => :second=,
+ surname_and_name: { join: ->{", "} }
+ }
+ }
+```
+First, we apply `RescueToEmptyString`, then `Pair`. In this case, if you set `nil`, name and surname will act as empty string values. For example:
+
+```ruby
+ce = ComplexExample.new(surname_name: nil)
+ce.name # => ""
+ce.surname_and_name # => ", "
+
+ce.name= "Isaac"
+ce.surname_and_name # => ", Isaac"
+ce.surname= "Asimov"
+ce.surname_and_name # => "Asimov, Isaac"
+
+ce.surname_name= nil
+ce.name # => ""
+ce.surname_and_name # => ", "
+```
+
+In this example it is safe set nil to `surname_name`. when we try to create the Pair, the [0] and [1] calls will return "", and we have one pair ["", ""]. This is why we add RescueToEmptyString first. If we use Pair as first trait, Pair expects an array, not a nil. *The Order is Important*.
+
## Hooks: after/before/around
Another great feature imported from Moose are the hooks after/before/around one method. You can run an arbitrary code, for example:
```ruby
@@ -532,27 +695,27 @@
def my_method(x)
# do something
end
- before :my_method do |object, x|
+ before :my_method do |this, x|
puts "#{Time.now} before my_method(#{x})"
end
- after :my_method do |object, x|
+ after :my_method do |this, x|
puts "#{Time.now} after my_method(#{x})"
end
end
```
### around (method| ARRAY) => lambda
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
```ruby
- around(:sum) do |method_lambda, object, a,b,c|
+ around(:sum) do |method_lambda, this, a,b,c|
c = 0
- result = method_lambda.call(object,a,b,c)
+ result = method_lambda.call(this,a,b,c)
result + 1
end
```
it is useful to manipulate the return value or argument list, add a begin/rescue block, aditional validations, etc, if you need.
@@ -970,10 +1133,10 @@
include MooseX
has event_handler: {
is: :ro,
isa: EventHandler,
- default: lambda{ EventHandler.new }, # EventProcessor HAS ONE EventHandler
+ default: -> { EventHandler.new }, # EventProcessor HAS ONE EventHandler
handles: { # Now, lets start to delegate and currying:
ping: { emit: :pinged }, # ping() is the same of event_handler.emit(:pinged)
pong: { emit: :ponged }, # pong(x) is the same of event_handler.emit(:pinged,x)
on_ping: { on: :pinged }, #
on_pong: { on: :ponged }, # same thing for on_ping / on_pong