Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
486 lines (364 sloc) 15.8 KB

= Organize these notes and docs == Categorize === public interface ==== features ===== existing features ====== bugs ===== planned features ===== experimental features ====== datatypes === implementation === rationale === other? = Notes on doodle See also ~/doodle-bugs.mm == Initialization

=== Notes Something I’ve just realised (which should have been obvious of course) is that definitions made in the singleton scope cannot use any of the features enabled during initialization (for example, validation & defaults).

I’ve put in a class_init method which does a basic initializaton after calling the block - but don’t want to repeat initialize_from_hash - (but borks on arg_order if I call that)

Need to address this in a more systematic way

=== DONE Initial values :init => value/proc {} ?

Do I need an initialize with default method? i.e. set an initial value - a different concept to the default value? i.e. set only once if no other value supplied.

This came up as I was using generated ids - I had to do this:

symbol :identifier

def initialize(*a, &b) self.identifier = “#{self.class}##{self.class.new_id}” super end

See file:~/interactive-fiction/world-model/world-model.rb::symbol identifier

where I think I’d rather do this:

symbol :identifier, :init => proc { “#{self.class}##{self.class.new_id}” }

Note, this is not the same as the :default (which only gets ‘realised’ when accessed). Perhaps I need to change the way :default works. Also note, this has the same problem as :default in singletons - it won’t happen because there is no :initialize call (except that I can interpret it on definition in singleton classes - but then I need to know that I’m being executed in context of singleton class - that one again… hmmm)

=== DONE Positional args :test:

Positional args should follow order in which attributes have been defined. Doesn’t seem to be doing that at the moment. So I need tests

Added arg_order to specify order of args in initialize()

Fixed parents & collect_inherited to return inherited classes in right order

=== TODO Fix parents and collect_inherited Fix parents & collect_inherited - they are a mess - need to tidy up/refactor/rethink/clarify/etc. === TODO Optional args :test: What to do about optional arguments?

==== reject raise error if unrecognised arguments in init

==== accept stash away somewhere so can be retrieved later

  • this is what is currently happening - creates instance vars for unrecognised values

==== ignore quietly discard - this is probably the least appealing option - don’t want to lose info without raising alert (e.g. could be misspelling)

=== TODO Inherited attributes :test:

Need better tests of inherited attributes - last two problems were to do with this

=== TODO Benchmarking :benchmark:

Profiling and benchmarking

  • how much does this cost?
  • where can I improve it?

Added profile task to rake: $ rake profile

=== TODO Document how it works :doc:

In particular, the way inheritance works (mainly for my own benefit :).

=== TODO Get 100% coverage :test:cov:

  • [ ] need to test inherited validations and conversions

=== TODO Validation of defaults See Attribute validation in doodle.rb

  • Defaults are not validated - should they be?
  • Also, because defaults are set on access, validation sets them - do I want this? means that e.g. setting a variable after initialization can cause validation error (see “should allow changing start_date without changing end_date” in spec/validation_2.rb) because now start_date > end_date (rather than just updating start_date and leaving end_date as default derived value) - this might be quite tricky to fix
  • if I don’t set default on access, then array defaults can be updated without setting instance_variable (which means the update is lost)
  • 2008-03-16: this has been addressed with :init => value option - defaults no longer create an instance variable

=== TODO Validate array members

  • do I want to enforce kind of array members? ‘typesafe’ containers?
    • e.g. ensure that all members of array are Things
    • 2008-03-16: this is partly addressed with :collect interface

=== DONE Singleton attribute defaults

  • because singleton objects do not have an initialize which is called, defaults don’t work (I think this is the reason) - fix this
  • now use class_init which will initialise from :init => values

=== TODO Conversions in initialization

I have Thing.from(other) - perhaps I could apply conversions in initialization so I can do Thing.new(other)

  • would need to be careful about infinite regression
  • need to test conversions on input

=== TODO Factory functions

Some wrinkles with factory functions - not always defined properly? Anonymous classes, etc. Need to test in modules, etc.

==== What is a factory function?

A factory function is a function that has the same name as a class which acts just like class.new. For example:

Cat(:name => ‘Ren’)

is the same as:

Cat.new(:name => ‘Ren’)

The notion of a factory function is somewhat contentious [xref ruby-talk], so while you get them automatically when deriving from Doodle::Base, you need to explicitly ask for them by including Factory in your base class if you use Doodle by including Doodle::Helper:

class Base < Doodle::Base end

class Dog < Base has :name end

stimpy = Dog(:name => ‘Stimpy’)

but

class Cat include Doodle::Helper include Doodle::Factory has :name end

ren = Cat(‘Ren’) etc.

==== More ideas about factory functions

Factory functions are not imported into current namespace when you include module.

==== Perhaps should change name to Constructor functions

=== DONE Collector

has :locations, :init => [], :collect => Location

should define a method :location like:

def location(*args, &block) locations << Location.new(*args, &block) end

has :locations, :init => [], :collect => {:location => Location}

=== TODO Nested hash initialization? === TODO Call initialization blocks on instantiation? i.e. use the fact that we have deferred processing to call the typedef blocks on object initialization rather than on definition? Would this work? Would get around the default vs init treatment of procs maybe?

== what else?

=== purpose of Attribute class

  • defines constraints on an object when a member of class
  • not standalone - rules it has to follow as member of set
  • maybe Constraint would be a better name
  • whereas object_validation is about whole object validity
  • attribute validity is as part of another object

=== initialization [3/3]

  • [X] block init
  • [X] init from hash - error handling
  • [X] positional args

=== attribute accessors [2/2]

  • [X] getter_setter
  • [X] default does not create an instance_variable
  • [X] init does create an instance_variable

=== defaults [4/4]

Unless a default is specified, an attribute is assumed to be required. You can specify :default => nil :init => nil if that is what you want.

  • [X] default value

– :default => value – :default => proc {} – do default value end – do default do … end end

  • [X] optional if has :default or :init, required otherwise
  • [X] enforce required
  • [X] When handling defaults, want to be able to execute default block in context of instance, e.g.

    class DateRange < Doodle::Root has :start_date, :kind => Date do default { Date.today } end has :end_date, :kind => Date do default { start_date } end end

    dr = DateRange.new dr.end_date # => Date.today

=== validation and conversion [7/7]

  • [X] kind
  • [X] validations
  • [X] conversions
  • [X] from
  • [X] must
  • [X] validate (Attribute validation)
  • [X] validate! (Object validation)
  • notes
    • (would rather have ‘should’ instead of ‘must’ but that’s taken by rspec :/)
    • use ancestor conversion if direct not available

=== user defined datatypes

These are not part of Doodle (yet) as there are issues involved with defining your own datatype declarations that I have not yet fully worked out. (I will probably end up using a separate block to enable datatype directives.)

However, to see what can be done, see ~/scratch/doodle/trunk/examples/datatype-defs.rb, ~/scratch/doodle/trunk/examples/datatypes.rb and ~/scratch/doodle/trunk/examples/application-model.rb

== DONE Hide behind extension __doodle__

Rather than pollute the instance variable namespace with my variables, I’ve chosen to hang all Doodle related info off another structure, i.e. the method :__doodle__ and access everything through that

This gives access to a DoodleInfo object which is stored centrally inside the DoodleInfo::DOODLES hash (keyed by object_id).

  • 2009-07-20 23:35:57 - not any more - this was preventing garbage collection

== DONE Attribute has :init => value as well as default

this is different to default - this sets the value in initialization and so creates an instance variable whereas default never creates an instance variable

=== DONE Specs

= What to show == normal instance attributes has :name == class attributes class_init do has :name end == singleton instance attributes o.singleton_class.has :name = Address community questions == Serialization == Compatibility with other libraries === ActiveRecord === Sequel === JRuby === Ruby 1.9 === Rubinius == How to do something comparable to has_many/belongs_to == Performance == Use cases === TODO Example real-world use cases = Thoughts about implementation == 2009-07-19 19:29:06

  • Union type - need some way to resolve when parsing, e.g. XML, to select which constructor to use
  • cf. parsing

== 2008-04-07 04:19:14

  • The more I have to hack up dirty workarounds, the more I feel frustrated with the limitations of Ruby 1.8
  • I’m tempted to move to 1.9 or Rubinius, though there’s no guarantee that what I’m trying to do would work there either

== 2008-04-07 Datatypes

  • Right now I’m trying to find a way to apply a mixin temporarily. This is so I can ‘hygenically’ create datatype definitions (e.g. use string :name instead of has :name, :kind => String). The best solution I’ve come up with so far is to create an anonymous class which includes the extensions I want, then copy over the resulting Attributes. I also need to copy the accessors and collectors too (not done yet).
  • I want to apply methods like #string inside a block, the result of which applies to the enclosing class definition, without extending the class to have a #string method.
  • One way would be to define the datatypes to generate data structures that represent the datatype definitions without applying them, i.e. just collect the parameters. This is made a little awkward by the fact you cannot pass blocks to blocks in 1.8 (because I wanted datatype definition to look something like this:

    define_datatype :date, :kind => Date do from String do |s| Date.parse(s) end from Array do |y,m,d| Date.new(y, m, d) end from Integer do |jd| Date.new(*Date.jd_to_civil(jd)) end end

    i.e. so you’re able to use #from and #must like in an ordinary Attribute definition.

  • I would then like to apply these definitions within an encapsulated namespace, like this:

    class DateRange < Base doodle do date :start date :end do default { start } end end end

    so that the definition of #date is valid only within the doodle block, but applies to the class. binding_of_caller would do the trick, but that’s not available any more.

    2008-04-07 07:51:53 - see examples/datatypes.rb for current approach using a proxy object

== 2008-04-12 23:46:19

  • trying to discover context, e.g. class << self or singleton class, etc.

== 2008-04-13 01:34:28

  • seems like the whole way I’m doing class and singleton attributes is wrong - keep hitting problems - maybe time for a complete rethink

== 2008-04-18 03:31:00

  • well, using a simpler superclass-based version of parents has resulted in 9 specs failing, all to do with singletons in one way or another. They are mostly somewhat suspect anyway I think. I’ll have to review them in detail.

    Perhaps now would be a good time to get my head straightened out on exactly what inheritance patterns make sense and were the metadata relating to them should be stored.

    Even though an attribute can be added to an instance’s singleton class, it should be visible through its attributes collection (e.g. instance.attributes) in the same way that a singleton method appears in the methods collection. So should probably rethink the interfaces too, e.g.

    attributesall, including inherited
    attributes(false)only attributes defined in class
    singleton_attributesonly singleton attributes
    instance_attributes?

    There are also the public_methods, protected_methods and private_methods methods. Doodle does not interact well with #private and #protected at the moment, so I’ll park those.

    (While I’m at it, I could probably get rid of all private methods and make them functions of __doodle__.)

    Also, #public_methods and #protected_methods could probably do with a clean out.

    Move to a more functional way of doing things - might be a bit tedious but would certainly remove some noise pollution.

    But certainly, I need to decide what I want for the singleton inheritance chain. Makes sense for classes to inherit methods defined in their superclass singleton classes (~ class methods). But does it make sense for there to be any inheritance chain at all for instance singletons? Shouldn’t they simply be like instances with their own special behaviour? Objects of a class of one.

= Inheritance

Bar.class_attributes = Bar.singleton_attributes + class attributes (inherited along superclass singletons) bar.class_attributes = Bar.class_attributes bar.attributes = bar.singleton_attributes + Bar.attributes Bar.attributes = attribute definitions

Bar.instance_attributes = ones that define instances Bar.singleton_attributes = ones defined only in Bar Bar.class_attributes = full list of class attributes

bar.attributes = bar.singleton_attributes + Bar.instance_attributes bar.instance_attributes = ones held at class level bar.singleton_attributes = singleton only bar.class_attributes = ones defined in class

when I ask for self.attributes, I want the ones that apply to self

so for Bar, I want its class_attributes for bar, I want the singleton_attributes + instance_attributes

then what happens inside singleton?

= 2008-04-18 16:25:57 Now have only a few tests failing, the ones relating to class attribute inheritance

= 2008-04-22 01:15:59 Got all specs working! Changed interpretation of klass.singleton_class.attributes to be consistent with instance.singleton_class.attributes to return only those attributes defined on singleton_class (so works similarly to local_attributes). To access full list of inherited class attributes, use klass.class_attributes

now make consistent throughout:

  • instance_attributes
  • class_attributes
  • singleton_attributes

and same with conversions and validations

= 2008-05-06 03:24:41

  • 0.1.4 release
    • keyed collections
    • specialized attributes

Of course, as soon as I release a version, I spot a bug - YAML loading data not working where attribute has a default - fixed now, but what a pain