Constructable Build Status

Provides a powerful class macro for defining and configuring constructable attributes of a class.

Basic usage

Define your class like this:

require 'constructable'
class ProgrammingLanguage
  constructable :creator, :name
end

Then you can construct objects by providing a hash as the last argument to ProgrammingLanguage.new like this:

ruby = ProgrammingLanguage.new(name: 'Ruby', creator: 'Yukihiro Matsumoto')

The object ruby will now have the instance variables @name and @creator set to 'Ruby' and 'Yukihiro Matsumoto'.

Setters, Getters

You can define your constructable attributes as readable, writable or both:

class Train
  constructable :speed, readable: true
  constructable :next_stop, writable: true
  constructable :location, accsessible: true
end

orient_express = Train.new(speed: 100)
orient_express.speed
#=> 100

orient_express.next_stop = 'Bucarest'
# @next_stop == "Bucarest"

orient_express.next_stop
# raises NoMethodError

orient_express.location = 'Budapest'
orient_express.location
#=> 'Budapest'

Does not break initialize behaviour

You can use initialize just like you'd normally do:

class Animal
  constructable [:biological_class, readable: true]
  attr_reader :name

  GuessBiologicalClass = { ['Pig', 'Cow', 'Whale'] => 'Mammal', [ 'Turtle', 'Caiman' ] => 'Reptile' }

  def initialize(name, options = {})
    @name = name
    @biological_class = GuessBiologicalClass.find { |animals,_| animals.include?(name) }.last if options[:guess_biological_class]
  end
end

rhinocerus = Animal.new('Rhinocerus', biological_class: 'Mammal')
rhinocerus.biological_class
#=> 'Mammal'

turtle = Animal.new('Turtle', guess_biological_class: true)
turtle.biological_class
#=> 'Reptile'

Validations

You can setup validation for constructable attributes, so the users of your api won't provide weird values or none at all:

required

class Holidays
  constructable :when, required: true
end

summer_holidays = Holidays.new
# raises AttributeError, ':when is a required attribute'

validate_type

class Conference
  constructable :attendees, validate_type: Integer
end

euruko = Conference.new('~300') # btw, euruko was really great!
# raises AttributeError, ':attendees must be of type Integer'

validate

class Farm
  costructable :animals,
    validate_type: Array,
    validate: ->(array_of_animals) do
      array_of_animals.all? { |animal| animal.is_a?(String) 
    end
end

big_farm = Farm.new(animals: [:pigs, :cows])
# raises AttributeError, ':animals has not passed validation'

Convert your attributes

You can pass a converter as an option for a constructable attribute, so before attributes are set, their values get converted to the return value of the proc, you provided:

class Box
  constructable :width, :height, converter: ->(value) { value.to_f * 100 }
end

small_box = Box.new(width: '1.40', height: '2.40')
small_box.width
#=> 140
small_box.height
#=> 240

Default values

You can also specify, which values your constructable attributes are set to by default:

class Framework
  constructable :opinionated, default: ->{true}
end

rails = Framework.new
rails.opinionated
#=> true

Redefining setters and getters

You can redefine the setters and getters provided by the constructable macro and still get all the validations and stuff by calling super:

class Song
  constructable :length, accessible: true, validate_type: Integer


  def length=(length)
    if length.is_a?(String) && length =~ /(\d{,2}):(\d{,2})/
      @length = $1.to_i * 60 + $2.to_i
    else
     super
    end
  end
end

song = Song.new(length: 190)
#=> #<Song:0x000001010ea040 @length=190>

song.length = '1:30'
song.length
#=> 90

song.length = 'abc'
# raises AttributeError, ':length must be of type Integer'

song = Song.new(name: 'Aaron', length: '6:01')
#=> #<Song:0x0x00000100941528 @length=190 @name="Aaron" @name_history=["Aaron"]>

constructable_attributes method

You can all the constructable attributes and their values of your class as a hash, by calling the constructable_attributes method from within an instance of your class:

class Gadget
  constructable :fancyness, :name, :price
end

iphone_4 = Gadget.new(fancyness: 1.0/0, name: 'Iphone 4', price: 1.0/0)
iphone_4.constructable_attributes
#=> { :fancyness => Infinity, :name => "Iphone 4", :price => Infinity }

Modules

WARNING

The constructable macro also works for modules(not for themself, but for the classes, you include them) but it will define the 'included' hook. So if you want to use the 'included' hook yourself, you either need to around alias it or to use another gem. I tried some things, but it is impossible, to provide all the magic of this gem, without defining the 'included' macro. I thought about getting rid of some of the magic, but this would infer with my initial intention, to provide an easy way to make constructable classes. If someone has a nice idea, how to solve this problem elgantly, please contact me!

Copyright (c) 2011 Manuel Korfmann. See LICENSE.txt for further details.