README.md in moosex-0.0.16 vs README.md in moosex-0.0.17

- old
+ new

@@ -1,9 +1,43 @@ # 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) +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) +## Introduction +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. + +Of course, there is few similar projects in ruby like + +- [Virtus](https://github.com/solnic/virtus) +- [Active Record Validations](http://edgeguides.rubyonrails.org/active_record_validations.html) + +But the objetive of MooseX is different: this is a toolbox to create Classes based on DSL, with unique features like + +- method delegation ( see 'handles') +- lazy attributes +- roles +- parameterized roles +- composable type check +- events + +and much more. + +This rubygem is based on this modules: + +- [Perl Moose](http://search.cpan.org/~ether/Moose-2.1204/lib/Moose.pm) +- [Perl Moo](http://search.cpan.org/~ether/Moose-2.1204/lib/Moose.pm) +- [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: + +- [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' :/ + THIS MODULE IS EXPERIMENTAL YET! BE CAREFUL! Talk is cheap. Show me the code! ```ruby @@ -110,13 +144,13 @@ to describe one new attribute you shoud specify some properties inside a Hash. The only mandatory property is the ':is', to specify how we should create the acessors (if public or private). The options for "has" are as follows: -### is +### is => ro|rw|rwp|private|lazy -**Required**, may be :ro, :rw, :rwp, :private or :lazy. +**Important**, may be :ro, :rw, :rwp, :private or :lazy. If you not specify, we will consider :rw, with all acessors with public visibility (**NEW**). "ro" specify a read-only attribute - generate only the reader method - you should specify the value in the constructor or using "default". "rw" specify a read-write attribute - generate both reader and writter methods. @@ -124,11 +158,11 @@ "private" will generate both reader and writter as private methods "lazy" similar to "ro", but also sets "lazy" to true and "builder" to "build_#{attribute_name}". -### isa +### isa => Class|lambda 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 @@ -139,11 +173,11 @@ 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 +### default => Constant|lambda 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. ```ruby default: 0, @@ -151,11 +185,11 @@ or ```ruby default: lambda{ MyObject.new }, ``` -### required +### required => true|false if true, the constructor will raise error if this attribute was not present. ```ruby required: true, @@ -163,11 +197,11 @@ if this attribute has a default value, we will initialize with this value and no exception will be raised. Optional. -### coerce +### 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 }, @@ -179,11 +213,11 @@ coerce: :to_i, ``` to force a convertion to integer. Or flatten one array, convert to symbol, etc. Optional. -### handles +### handles => Array|Hash|Class|Module One of the greatest features in MooseX: you can inject methods and delegate the method calling to the attribute. For example, instead do this: ```ruby def some_method(a,b,c) @@ -262,11 +296,11 @@ obj.target.method1([1,2,3],2,3) # OR obj.my_method_1(2,3) ``` are equivalent. -### trigger +### 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| @@ -288,19 +322,19 @@ end ``` Optional. -### writter +### writter => true|method name -You can specify the name of the attribute acessor, default is "#{attribute_name}=". +You can specify the name of the attribute acessor, default is "#{attribute_name}=" (if true). -### reader +### reader => true|method name -You can specify the name of the attribute acessor, default is "attribute_name". +You can specify the name of the attribute acessor, default is "attribute_name" (if true). -### predicate +### predicate => true|method name 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. For example ```ruby @@ -320,11 +354,11 @@ ``` Important: nil is different than undefined. If you do not initialize one attribute, you will receive one 'nil' if you try to fetch the value, but the state of this attribute is 'undefined'. If you set any value (even nil), the attribute will be considered 'defined' and the predicate will return true. Optional. -### clearer +### clearer => true| attribute name 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: ```ruby class Foo @@ -344,11 +378,11 @@ foo.clear_x! # will unset the attribute x foo.has_x? # returns false ``` Optional. -### init_arg +### init_arg => "new attribute name" You can rename the attribute name in the constructor. For example: ```ruby class Foo @@ -365,11 +399,11 @@ foo = Foo.new(x: 1) # specify the value of secret in the constructor foo.x # return 1 foo.x= 2 # will set 'secret' to 2 ``` -### lazy +### lazy => true|false 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: ```ruby class Foo @@ -398,11 +432,11 @@ ``` A lazy attribute needs a builder method or lambda. By default you should implement the "builder_#{attribute_name}" method. Using lazy you should initialize one attribute when you really need. Optional. -### builder +### builder => method name|lambda 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. ```ruby class Foo @@ -415,10 +449,41 @@ } end ``` 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. + +```ruby +class Foo + include MooseX + has x: { is: :rw, weak: true } +end + +f = Foo.new(x: Object.new) + +puts f.x.class # will be WeakRef +GC.start +puts f.x # may raise exception (recycled) +``` + +You should verify with `weakref_alive?` method to avoid exceptions. + +Optional. + +## doc => String + +You can add a string metadata about the attribute. It will be stored, and you can use this in a near future. + +Optional. + +## override => true|false + +If you need override one attribute, you should use `override: true`, or MooseX will raise one exception. + ## 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 @@ -449,15 +514,15 @@ Roles and Hooks 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!!! -### after +### after (method| ARRAY) => lambda 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 +### before (method| ARRAY) => lambda 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: @@ -476,11 +541,11 @@ puts "#{Time.now} after my_method(#{x})" end end ``` -### around +### 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| @@ -689,13 +754,13 @@ ### requires 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. -## Parametric Roles +## Parameterized Roles -Parametric 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. +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. ```ruby module EasyCrud include MooseX @@ -711,13 +776,13 @@ ... ``` 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?` -### composable parametric roles +### composable parameterized roles -To combine one or more parametric roles to another parametric role you should do something like this: +To combine one or more parameterized roles to another parameterized role you should do something like this: ```ruby module Logabble2 include MooseX @@ -887,26 +952,30 @@ ep.ping # will print "receive ping!" ep.pong 1 # will print "receive pong with 1!" ``` -Now, imagine what you can do with a Parametrized Role: we can create all handles based on event names! +Now, imagine what you can do with a parameterized role: we can create all handles based on event names! ## IMPORTANT This module is experimental. I should test more and more to be possible consider this "production ready". If you find some issue/bug please add here: https://github.com/peczenyj/MooseX/issues Until the first 0.1 version I can change anything without warning. I am open to suggestions too. +## Mailing List + +https://groups.google.com/d/forum/moosex-ruby-dev-list + ## TODO -1. Support to Roles ( it is a Module on Steroids ) -2. Support to after/before/around -3. Improve the typecheck system (we should specify: we need an array of positive integers) -4. Improve the exception and warning system +1. Support to Roles ( it is a Module on Steroids ) [done] +2. Support to after/before/around [done] +3. Improve the typecheck system (we should specify: we need an array of positive integers) [done] +4. Improve the exception and warning system [in progress] 5. Profit! ## Limitations Experimental module, be careful. @@ -918,5 +987,11 @@ 1. Fork it ( http://github.com/peczenyj/MooseX/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request + +## About the Author + +[@pac_man](https://twitter.com/pac_man) + +[about.me](http://about.me/peczenyj)