README.md in moosex-0.0.10 vs README.md in moosex-0.0.11

- old
+ new

@@ -1,8 +1,8 @@ # MooseX -A postmodern object DSL for Ruby [![Build Status](https://travis-ci.org/peczenyj/MooseX.png)](https://travis-ci.org/peczenyj/MooseX) +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) THIS MODULE IS EXPERIMENTAL YET! BE CAREFUL! Talk is cheap. Show me the code! @@ -135,11 +135,10 @@ isa: lambda do |new_value| unless new_value.respond_to? :to_sym raise "bar should respond to to_sym method!" end end, -end ``` 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. ### default @@ -152,10 +151,22 @@ or ```ruby default: lambda{ MyObject.new }, ``` +### required + +if true, the constructor will raise error if this attribute was not present. + +```ruby + required: true, +``` + +if this attribute has a default value, we will initialize with this value and no exception will be raised. + +Optional. + ### coerce You can try to coerce the attribute value by a lambda before the type check phase. For example you can do ```ruby @@ -193,11 +204,13 @@ ### trigger 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 {|object, new_value| object.logger.log "change the attribute value to #{new_value}" } + trigger: lambda do |object, new_value| + object.logger.log "change the attribute value to #{new_value}" + end ``` or ```ruby has a: { is: :rw } has b: { @@ -280,11 +293,11 @@ has secret: { is: :rw, writter: :x=, reader: :x, - init_art: :x, + init_arg: :x, } end foo = Foo.new(x: 1) # specify the value of secret in the constructor foo.x # return 1 @@ -339,10 +352,224 @@ } end ``` Optional. +## 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 +class Point + include MooseX + + has [:x, :y ], { is: :rw, required: true } + + def clear! + self.x = 0 + self.y = 0 + end +end + +class Point3D < Point + + has z: { is: :rw, required: true } + + after :clear! do |object| + object.z = 0 + end +end +``` + +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. + +### after + +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. + +### before + +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. + +A good example should be logging: + +```ruby +class Point + include MooseX + + def my_method(x) + # do something + end + + before :my_method do |object, x| + puts "#{Time.now} before my_method(#{x})" + end + after :my_method do |object, x| + puts "#{Time.now} after my_method(#{x})" + end +end +``` + +### around + +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 + +```ruby + around(:sum) do |original_method, object, a,b,c| + result = original_method.bind(object).call(a,b,c) + result + 1 + end +``` + +it is useful to manipulate the return value if you need. + +## Types + +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: + +### isAny + +will accept any type. Useful to combine with other types. + +```ruby + has x: { is: :rw, isa: isAny } +``` + +### isConstant + +will verify using :=== if the value is equal to some contant + +```ruby + has x: { is: :rw, isa: isConstant(1) } +``` + +### isType, isInstanceOf and isConsumerOf + +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. + +```ruby + has x: { is: :rw, isa: isConsumerOf(Enumerable) } +``` + +### hasMethods + +will verify if the value respond_to? for one or more methods. + +```ruby + has x: { is: :rw, isa: hasMethods(:to_s) } +``` +### isEnum + +verify if the value is part of one enumeration of constants. + +```ruby + has x: { is: :rw, isa: isEnum(:black, :white, :red, :green, :yellow) } +``` + +### isMaybe(type) + +verify if the value isa type or is nil. You can combine with other types. + +```ruby + has x: { is: :rw, isa: isMaybe(Integer) } # accepts 1,2,3... or nil +``` + +### isNot(type) + +will revert the type check. Useful to combine with other types + +```ruby + has x: { + is: :rw, # x will accept any values EXCEPT :black, :white... + isa: isNot(isEnum(:black, :white, :red, :green, :yellow)) + } +``` + +### isArray(type) + +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. + +```ruby + has x: { + is: :rw, + isa: isArray() # will accept any array + } + has y: { + is: :rw, # this is a more complex type + isa: isArray(isArray(isMaybe(Integer))) + } +``` + +### isHash(type=>type) + +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. + +```ruby + has x: { + is: :rw, + isa: isHash() # will accept any Hash + } + has y: { + is: :rw, # this is a more complex type + isa: isHash(Integer => isArray(isMaybe(Integer))) + } +``` + +## isSet(type) + +similar to isSet. the difference is: it will raise one exception if there are non unique elements in this array. + +```ruby + has x: { + is: :rw, + isa: isSet(Integer) # will accept [1,2,3] but not [1,1,1] + } +``` + +## isTuple(types) + +similar to isArray, Tuples are Arrays with fixed size. We will verify the type of each element. + +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: + +```ruby + has x: { + is: :rw, + isa: isTuple(Integer, Symbol, isMaybe(String)) + } +``` + +### isAllOf(types) + +will combine all types and will fail if one of the condition fails. + +```ruby + has x: { + is: :rw, + isa: isAllOf( + hasMethods(:foo, :bar), # x should has foo and bar methods + isConsumerOf(Enumerable), # AND should be an Enumerable + isNot(SomeForbiddenClass), # AND and should not be an SomeForbiddenClass + ) + } +``` + +### isAnyOf(types) + +will combine all types and will fail if all of the condition fails. + +```ruby + has x: { + is: :rw, + isa: isAnyOf( + hasMethods(:foo, :bar), # x should has foo and bar methods + isEnum(1,2,3,4), # OR be 1,2,3 or 4 + isHash(isAny => Integer) # OR be an Hash of any type => Integers + ) + } +``` + ## BUILD If you need run some code after the creation of the object (like some extra validation), you should override the BUILD method. ```ruby @@ -360,10 +587,10 @@ end end b1 = BuildExample.new(x: 1, y: 2) # will create the object b2 = BuildExample.new(x: 1, y: 1) # will raise the exception! -```ruby +``` ### BUILDARGS If you need manupulate the constructor argument list, you should implement the method BUILDARGS. You MUST return one Hash of attribute_name => value.