# DSL - class-level methods that affect instances

**URL:** <https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432>\
**Category:** Ruby\
**Created:** [May 2, 2008, 1:07am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432 "2008-05-02T01:07:11Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 1:07am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/1 "2008-05-02T01:07:11Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 2:41am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/2 "2008-05-02T02:41:01Z")

</div>

On Thu, 01 May 2008 18:07:11 -0500, Daniel W. 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”}

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 2:55am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/3 "2008-05-02T02:55:45Z")

</div>

Ken B. wrote:

> On Thu, 01 May 2008 18:07:11 -0500, Daniel W. 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…

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 5:10am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/4 "2008-05-02T05:10:50Z")

</div>

On Thu, 01 May 2008 19:55:45 -0500, Daniel W. wrote:

> > def self.translate word, params  
> > translate ‘c’, :to =\> ‘d’  
> > @@translations ||= {}  
> > class C \< Translator::Base  
> > 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

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 5:25am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/5 "2008-05-02T05:25:26Z")

</div>

Hi –

On Fri, 2 May 2008, Ken B. wrote:

> > > class C \< Translator::Base
> > 
> > end  
> > translate ‘this’, :to =\> ‘that’
> 
> 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

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 5:35am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/6 "2008-05-02T05:35:41Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 3:45am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/7 "2008-05-02T03:45:22Z")

</div>

Hi –

On Fri, 2 May 2008, Daniel W. wrote:

> class C
> 
> 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

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 6:19am UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/8 "2008-05-02T06:19:53Z")

</div>

Daniel W. 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!

---

<div class="post-metadata">

**Author:** ![dewie](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ac8455/32.png) [@dewie](https://www.ruby-forum.com/u/dewie)\
**Post date:** [May 2, 2008, 12:25pm UTC](https://www.ruby-forum.com/t/dsl-class-level-methods-that-affect-instances/136432/9 "2008-05-02T12:25:39Z")

</div>

On 2 May 2008, at 00:07, Daniel W. 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:

> <https://github.com/rails/rails/tree/main/activesupport/lib>
>
> //github.com/rails/rails/tree/main/activesupport/lib

active\_support/core\_ext/class/inheritable\_attributes.rb

They do what (I think) you want.

Regards,  
Andy S.
