#+TAGS: test(t) benchmark(b) doc(d) cov(c) feature(f)
= 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 [[file:~/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%20identifier][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 [[file:lib/doodle.rb::def%20parents][parents]] & [[file:lib/doodle.rb::def%20collect_inherited%20message][collect_inherited]] to return inherited classes in
right order
=== TODO Fix parents and collect_inherited
Fix [[file:lib/doodle.rb::def%20parents][parents]] & [[file:lib/doodle.rb::def%20collect_inherited%20message][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 [[file:lib/doodle.rb::bump%20off%20validate%20for%20Attributes%20maybe%20better%20way%20of%20doing][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
[[file:~/scratch/doodle/trunk/examples/datatype-defs.rb]],
[[file:examples/datatypes.rb][file:~/scratch/doodle/trunk/examples/datatypes.rb]] and
[[file:examples/application-model.rb][file:~/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.
| attributes | all, including inherited |
| attributes(false) | only attributes defined in class |
| singleton_attributes | only 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