-
Notifications
You must be signed in to change notification settings - Fork 6
builder
Gorillib::Builder
provides foundation models for elegant ruby DSLs (Domain-Specific Languages). A builder block's relaxed ruby syntax enables highly-readable specification of complex behavior:
```ruby
garage do
car :ford_tudor do
manufacturer 'Ford Motor Company'
car_model 'Tudor'
year 1939
doors 2
style :sedan
engine do
displacement 350
cylinders 8
end
end
car :wildcat do
manufacturer 'Buick'
car_model 'Wildcat'
year 1968
doors 2
style :convertible
engine do
displacement 455
cylinders 8
end
end
end
```
To make a class be a builder, simply include Gorillib::Builder
:
```ruby
class Garage
include Gorillib::Builder
collection :car
end
class Car
include Gorillib::Builder
field :name, String
field :manufacturer, String
field :car_model, String
field :year, Integer
field :doors, Integer
field :style, Symbol, :validates => { :inclusion_in => [:sedan, :coupe, :convertible] }
member :engine
belongs_to :garage
end
class Engine
include Gorillib::Builder
field :displacement, Integer
field :cylinders, Integer
belongs_to :car
end
```
Fields of a Builder class create a single "getset" accessor (and not the familiar 'foo/foo=' pair):
-
car_model.foo
-- returns value of foo -
car_model.foo(val)
-- sets foo toval
, which can be any value, evennil
.
A builder class can have member
fields; the type of a member field should be a builder class itself
With no arguments, the accessor returns the value of engine attribute, creating if necessary:
```ruby
car.engine #=> #<Engine displacement=~ cylinders=~>
```
If you pass in a hash of values, the engine is created if necessary and then asked to receive!
the hash.
```ruby
car.engine #=> #<Engine displacement=~ cylinders=~>
car.engine(:cylinders => 8) #=> #<Engine displacement=~ cylinders=8>
car.engine.cylinders #=> 8
```
If you provide a no-args block, it is instance_eval
ed in the context of the member object:
```ruby
car :ford_tudor do
engine(:cylinders => 8) do
self #=> #<Engine displacement=~ cylinders=8>
displacement 455
cylinders self.cylinders - 2
self #=> #<Engine displacement=455 cylinders=6>
end
engine #=> #<Engine displacement=455 cylinders=6>
end
```
Some people disapprove of instance_eval
, as they consider it unseemly to mess around with self
. If you instead provide a one-arg block, the member object is passed in:
```ruby
car :ford_tudor do |c|
c.engine(:cylinders => 8) do |eng|
self #=> #<Car ...>
eng.displacement 455
eng.cylinders eng.cylinders - 2
eng #=> #<Engine displacement=455 cylinders=6>
end
c.engine #=> #<Engine displacement=455 cylinders=6>
end
```
A builder class can also have collection
fields, to contain named builder objects.
```ruby
garage do
car(:ford_tudor) #=> #<Car name="ford tudor" ...>
cars #=> { :ford_tudor => #<Car ...>, :wildcat => #<Car ...> }
end
```
The collected items must respond to id
(FIXME:). The singular accessor accepts arguments just like a member
accessor:
```ruby
garage do
car(:ford_tudor, :year => 1939)
#=> #<Car name=`<:ford_tudor year=1939 doors=~ ...>
car(:ford_tudor, :year => 1939) do
doors 2
style :convertible
end
#=> #<Car name="ford tudor" year=1939 doors=2 ...>
car(:wildcat, :year => 1968) do |c|
c.doors 2
c.style :convertible
end
#=> #<Car name= year=1939 doors=2 ...>
builders have the following:
Record::Defaults
Model::Naming
Model::Conversion
When a subclass happens, decorates class with saucepot(...)
, equivalent to
update_or_create
registers
- At class level,
registry_for(CookingUtensil)
gives-
add_cooking_utensil()
and so forth for adding and getting - Protected
cooking_utensils
hash class attribute
-
- Enlisting a resource class (Kitchen.register(FryingPan) and gives you a magic
frying_pan
factory method with signaturefrying_pan(name, *args, &block)
- If resource does not exist, calls
FryingPan.new
with full set of params and block. Add it tocooking_utensils
registry. - If resource with that name exists, retrieve it. call
merge()
with the parameters, andrun_in_scope
with the block.
- If resource does not exist, calls