Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > DSL - class-level methods that affect instances

Reply
Thread Tools

DSL - class-level methods that affect instances

 
 
Daniel Waite
Guest
Posts: n/a
 
      05-01-2008
I'm having a problem of design. I've tried a few configurations of class
collaboration and responsibility, but they all end up feeling messy.

I'm looking at these (either or, my original attempt was with a module,
but attr_accessor *looks* like it does what I want):

class C < Translator::Base
translate 'this', :to => 'that'
end

class C
include Translator
translate 'this', :to => 'that'
end

With the expectation that...

c = C.new
c.translations

Would yield a hash of the from and to values.

I have no idea how to break the barrier between class and instance.

Any ideas?
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
 
 
 
Ken Bloom
Guest
Posts: n/a
 
      05-02-2008
On Thu, 01 May 2008 18:07:11 -0500, Daniel Waite wrote:

> I'm having a problem of design. I've tried a few configurations of class
> collaboration and responsibility, but they all end up feeling messy.
>
> I'm looking at these (either or, my original attempt was with a module,
> but attr_accessor *looks* like it does what I want):
>
> class C < Translator::Base
> translate 'this', :to => 'that'
> end
>
> class C
> include Translator
> translate 'this', :to => 'that'
> end
>
> With the expectation that...
>
> c = C.new
> c.translations
>
> Would yield a hash of the from and to values.
>
> I have no idea how to break the barrier between class and instance.


Use class variables (the @@ variety).

class Translator::Base
def self.translate word, params
@@translations ||= {}
@@translations[word]=params[:to]
end
def translations
@@translations ||= {}
end
end

class C < Translator::Base
translate 'a', :to => 'b'
translate 'c', :to => 'd'
end

C.new.translations #=> {"a"=>"b", "c"=>"d"}


--
Ken (Chanoch) Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/
 
Reply With Quote
 
 
 
 
Daniel Waite
Guest
Posts: n/a
 
      05-02-2008
Ken Bloom wrote:
> On Thu, 01 May 2008 18:07:11 -0500, Daniel Waite wrote:
>
>> class C
>>
>> I have no idea how to break the barrier between class and instance.

>
> Use class variables (the @@ variety).
>
> class Translator::Base
> def self.translate word, params
> @@translations ||= {}
> @@translations[word]=params[:to]
> end
> def translations
> @@translations ||= {}
> end
> end
>
> class C < Translator::Base
> translate 'a', :to => 'b'
> translate 'c', :to => 'd'
> end
>
> C.new.translations #=> {"a"=>"b", "c"=>"d"}


That works! Except it shares translations across all subclasses.

module Translator
class Base

def self.translate word, params
@@translations ||= {}
@@translations[word]=params[:to]
end

def translations
@@translations ||= {}
end

end
end

class C < Translator::Base

translate 'a', :to => 'b'
translate 'c', :to => 'd'

end

class D < Translator::Base

translate 'this', :to => 'that'

end

irb(main):028:0> c = C.new
=> #<C:0x4d4e0>
irb(main):029:0> c.translations
=> {"a"=>"b", "c"=>"d", "this"=>"that"}

While I understand the behavior, its undesirable. I'm going to dust off
Ruby for Rails...
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
David A. Black
Guest
Posts: n/a
 
      05-02-2008
Hi --

On Fri, 2 May 2008, Daniel Waite wrote:

> I'm having a problem of design. I've tried a few configurations of class
> collaboration and responsibility, but they all end up feeling messy.
>
> I'm looking at these (either or, my original attempt was with a module,
> but attr_accessor *looks* like it does what I want):
>
> class C < Translator::Base
> translate 'this', :to => 'that'
> end
>
> class C
> include Translator
> translate 'this', :to => 'that'
> end
>
> With the expectation that...
>
> c = C.new
> c.translations
>
> Would yield a hash of the from and to values.
>
> I have no idea how to break the barrier between class and instance.


I'd say: don't think of it as any more of a barrier than exists
between any other two objects. Every object, including a class object,
has the right to maintain state, and to decide what it will and will
not expose. There's no privileged relationship between c and i just
because i is an instance of c.

(I know that sounds kind of doctrinaire, but I like to lay it on thick
since it usually takes some doing to chip away at the presumptive
class/instance special relationship

As Ken suggested, class variables break through the class/instance
boundaries. That's one of the reasons I don't like them You can
certainly do it that way; I'll also show you another possibility,
based on engineering all the objects concerned so that they query each
other where necessary:

module Translator
attr_reader :translations

def translate(target, actions)
@translations ||= []
@translations.push("Translating #{target} to #{actions[:to]}")
end
end

class C
extend Translator

def translations
self.class.translations
end

translate "this", :to => "that"
end

p C.new.translations # ["Translating this to that"]


David

--
Rails training from David A. Black and Ruby Power and Light:
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
INTRO TO RAILS June 24-27 London (Skills Matter)
See http://www.rubypal.com for details and updates!

 
Reply With Quote
 
Ken Bloom
Guest
Posts: n/a
 
      05-02-2008
On Thu, 01 May 2008 19:55:45 -0500, Daniel Waite wrote:

> Ken Bloom wrote:
>> On Thu, 01 May 2008 18:07:11 -0500, Daniel Waite wrote:
>>
>>> class C
>>>
>>> I have no idea how to break the barrier between class and instance.

>>
>> Use class variables (the @@ variety).
>>
>> class Translator::Base
>> def self.translate word, params
>> @@translations ||= {}
>> @@translations[word]=params[:to]
>> end
>> def translations
>> @@translations ||= {}
>> end
>> end
>>
>> class C < Translator::Base
>> translate 'a', :to => 'b'
>> translate 'c', :to => 'd'
>> end
>>
>> C.new.translations #=> {"a"=>"b", "c"=>"d"}

>
> That works! Except it shares translations across all subclasses.
>
> module Translator
> class Base
>
> def self.translate word, params
> @@translations ||= {}
> @@translations[word]=params[:to]
> end
>
> def translations
> @@translations ||= {}
> end
>
> end
> end
>
> class C < Translator::Base
>
> translate 'a', :to => 'b'
> translate 'c', :to => 'd'
>
> end
>
> class D < Translator::Base
>
> translate 'this', :to => 'that'
>
> end
>
> irb(main):028:0> c = C.new
> => #<C:0x4d4e0>
> irb(main):029:0> c.translations
> => {"a"=>"b", "c"=>"d", "this"=>"that"}
>
> While I understand the behavior, its undesirable. I'm going to dust off
> Ruby for Rails...


I couldn't remember why I didn't use @@ variables for another very
similar metaprogramming problem I had this week. Now you've reminded why.

This version uses instance variables on the singleton class (which is
different from using @@ class variables)

class Translator::Base
def self.translate word, params
@translations ||= {}
@translations[word]=params[:to]
end
def translations
self.class.instance_variable_get(:@translations) || {}
end
end

class C < Translator::Base
translate 'a', :to => 'b'
translate 'c', :to => 'd'
end

C.new.translations #=> {"a"=>"b", "c"=>"d"}

class D < Translator::Base
translate 'e', :to => 'f'
end

D.new.translations #=> {"e"=>"f"}
C.new.translations #=> {"a"=>"b", "c"=>"d"}

#But beware:
class E < C
translate 'g', :to => 'h'
end

E.new.translations #=> {"g"=>"h"}

There are ways to solve this by explicitly asking for your parent class's
translations, if you'd like to have that functionality.

--Ken

--
Ken (Chanoch) Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/
 
Reply With Quote
 
David A. Black
Guest
Posts: n/a
 
      05-02-2008
Hi --

On Fri, 2 May 2008, Ken Bloom wrote:

> On Thu, 01 May 2008 19:55:45 -0500, Daniel Waite wrote:
>
>> Ken Bloom wrote:
>>> On Thu, 01 May 2008 18:07:11 -0500, Daniel Waite wrote:
>>>
>>>> class C
>>>>
>>>> I have no idea how to break the barrier between class and instance.
>>>
>>> Use class variables (the @@ variety).
>>>
>>> class Translator::Base
>>> def self.translate word, params
>>> @@translations ||= {}
>>> @@translations[word]=params[:to]
>>> end
>>> def translations
>>> @@translations ||= {}
>>> end
>>> end
>>>
>>> class C < Translator::Base
>>> translate 'a', :to => 'b'
>>> translate 'c', :to => 'd'
>>> end
>>>
>>> C.new.translations #=> {"a"=>"b", "c"=>"d"}

>>
>> That works! Except it shares translations across all subclasses.
>>
>> module Translator
>> class Base
>>
>> def self.translate word, params
>> @@translations ||= {}
>> @@translations[word]=params[:to]
>> end
>>
>> def translations
>> @@translations ||= {}
>> end
>>
>> end
>> end
>>
>> class C < Translator::Base
>>
>> translate 'a', :to => 'b'
>> translate 'c', :to => 'd'
>>
>> end
>>
>> class D < Translator::Base
>>
>> translate 'this', :to => 'that'
>>
>> end
>>
>> irb(main):028:0> c = C.new
>> => #<C:0x4d4e0>
>> irb(main):029:0> c.translations
>> => {"a"=>"b", "c"=>"d", "this"=>"that"}
>>
>> While I understand the behavior, its undesirable. I'm going to dust off
>> Ruby for Rails...

>
> I couldn't remember why I didn't use @@ variables for another very
> similar metaprogramming problem I had this week. Now you've reminded why.
>
> This version uses instance variables on the singleton class (which is
> different from using @@ class variables)


I wish that class variables were $$ instead of @@. They're really a
kind of restricted global, and they have the same qualities of sprawl
that globals do. The @@ always makes it look like they have some kind
of kinship with instance variables, when in fact they're practically
the opposite, in that they open up a shared space for storing state
whereas instance variables specifically make it possible for objects
to control state on an individual, private (give or take the fact that
nothing is really private in Ruby) basis.


David

--
Rails training from David A. Black and Ruby Power and Light:
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
INTRO TO RAILS June 24-27 London (Skills Matter)
See http://www.rubypal.com for details and updates!

 
Reply With Quote
 
Daniel Waite
Guest
Posts: n/a
 
      05-02-2008
David A. Black wrote:
> I'd say: don't think of it as any more of a barrier than exists
> between any other two objects.


That helps, actually. Viscerally, for now, but I'm sure the fullness of
the stance will sink in with time.

> (I know that sounds kind of doctrinaire, but I like to lay it on thick
> since it usually takes some doing to chip away at the presumptive
> class/instance special relationship


I love saturated opinions. (David West all the way!) I'm re-reading your
book right now starting from chapter 5, and yes, I can already tell I've
lost a forgotten a lot of my understanding of how Ruby works. (I suppose
you could argue I never understood it if I forgot...?)

> module Translator
> attr_reader :translations
>
> def translate(target, actions)
> @translations ||= []
> @translations.push("Translating #{target} to #{actions[:to]}")
> end
> end
>
> class C
> extend Translator
>
> def translations
> self.class.translations
> end
>
> translate "this", :to => "that"
> end
>
> p C.new.translations # ["Translating this to that"]


Works beautifully. Amazing. I thought I was going to end up with some
monolithic structure like ActiveRecord.

@Ken:

Your solution works equally well! I like that yours is class-based so I
don't have to call extend in the body of each inheriting class (of which
there will be quite a few).

I'm not worried about tertiary inheritance; I don't plan on needing it.
(Famous last words, right?)

Thank you both, Ken and David, very much!
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Daniel Waite
Guest
Posts: n/a
 
      05-02-2008
Daniel Waite wrote:
> Thank you both, Ken and David, very much!


Here she is, best of both approaches!

module Translator

module ClassMethods
attr_reader :translations

def translate(from_word, options)
@translations ||= Hash.new
@translations[from_word] = options[:to]
end
end

def self.included(receiver)
receiver.extend(ClassMethods)
end

def translations
self.class.translations
end

class Base
include Translator
end

end

class NewTranslator < Translator::Base

translate 'customer_profile_id', :to => 'customerProfileId'

end

p NewTranslator.new.translations

I feel a little odd putting the class Base declaration at the end of the
module, but if it's not there it has to be outside it entirely (and it
must still occur _after_ the code inside the module), something I don't
really want to do.

Anywho, thanks again for the awesome help. You guys rock!
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Andrew Stewart
Guest
Posts: n/a
 
      05-02-2008

On 2 May 2008, at 00:07, Daniel Waite wrote:

> I have no idea how to break the barrier between class and instance.


You might be interested by Active Support's class inheritable
attributes:

http://github.com/rails/rails/tree/m...vesupport/lib/
active_support/core_ext/class/inheritable_attributes.rb

They do what (I think) you want.

Regards,
Andy Stewart

-------
AirBlade Software
http://airbladesoftware.com





 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
problem in running a basic code in python 3.3.0 that includes HTML file Satabdi Mukherjee Python 1 04-04-2013 07:48 PM
dicts,instances,containers, slotted instances, et cetera. ocschwar@gmail.com Python 8 01-29-2009 09:52 AM
Is there a way to find the class methods of a class, just like'methods' finds the instance methods? Kenneth McDonald Ruby 5 09-26-2008 03:09 PM
methods that affect subsequently defined methods M. Ayhan Ruby 1 03-08-2007 11:06 PM
list of class instances within a list of a class instances John Wohlbier Python 2 02-22-2004 08:41 AM



Advertisments