Forum: Ruby on Rails Ruby, Rails & Inheritance

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
9414814c1a52779f75cef5d29b0e9b40?d=identicon&s=25 Dim (Guest)
on 2006-02-07 12:10
Hi!

I am looking for a solution for the following problem:

Some of my models share some attributes and also was in need for some
extra features, so I created a class "RecordWithFeatures":
-----------------------------------------------------------------
class RecordWithFeatures < ActiveRecord::Base
    # @Override
  def self.descends_from_active_record?
    superclass == RecordWithFeatures
  end

    # @Override
  def self.class_name_of_active_record_descendant(klass)
    ...
  end
  ...
end

class SomeModel < RecordWithFeatures
...
end
-----------------------------------------------------------------

Ok, everything works fine, but I would also like to pass some
class-methods to my models as soon as the inherit RecordWithFeatures.

Example:
All models have an attribute "context_id", so every model includes these
2 lines
--------------------------------------
belongs_to :context
validates_presence_of :context
--------------------------------------

What can I do to add those automatically? If I include them into the
RecordWithFeatures class, they are valid for RecordWithFeatures only,
but it's not RecordWithFeatures, but it's children, that should belong
to "context".

Thanks for all hints
Dim
8a4c59ead55636dcf3f82fe0e545f5ed?d=identicon&s=25 Byron Saltysiak (Guest)
on 2006-02-07 12:58
(Received via mailing list)
A nice construct in Ruby for sharing code between classes but without
the
messyness of requiring a common ancestor is the mixin pattern. (
http://www.rubycentral.com/book/tut_modules.html)

If there are elements that you'd like to share you can make it more
clear
what is going on via something like the the following. The only thing is
I'm
not 100% sure it will work with the belongs_to and validates_presence_of
-
let me know!

module ContextRecord
  belongs_to :context
  validates_presences_of :context

  def foo
    # shared method...
  end
end

class SomeModel < ActiveRecord::Base
  include ContextRecord
end

- Byron
9414814c1a52779f75cef5d29b0e9b40?d=identicon&s=25 Dim (Guest)
on 2006-02-07 15:14
Thanks for the reply, but it's not that easy as it seems. My
RecordWithFeatures overloads the "find" method. Implementing the
functionality as a module would completely overWRITE the find method.

I had already a discussion on that on this list, it's not trivial,
because "find" uses recursive calls, so there are two options for the
implementation.

First (as I did), subclassing AR and overriding find. The second (I also
tried) writing a module and aliasing the find method. The second one
works great, but from a coder's point of view, it is really messy,
because in some cases it can break "find" completetly.

Now I post both solutions, maybe someone have an idea how (if) those can
be improved.

The first one: Extending AR
------------------------------------
class RecordWithFeatures < ActiveRecord::Base
  def self.descends_from_active_record?
    superclass == RecordWithFeatures
  end
  def self.class_name_of_active_record_descendant(klass)
    ...
  end

  def self.without_context_scope
    begin
      Thread.current[:bypass_context_scope] = true
      result = yield
    ensure
      Thread.current[:bypass_context_scope] = nil
    end
    return result
  end

  def self.bypass_context_scope?
    Thread.current[:bypass_context_scope] == true
  end

  def self.find(*args)
    unless bypass_context_scope?
      with_scope(:find => {:conditions => "..."}) do
        return without_context_scope { super }
      end
    end
    super
  end

  def self.find_context_neutral(*args)
    return without_context_scope { find(*args) }
  end
end

class SomeModel << RecordWithFeatures
    belongs_to :context
    validates_presence_of :context
end
------------------------------------


And now the other one with "including" and "aliasing"!
------------------------------------
module ContextRecord
  def self.included(base) # :nodoc:
    base.extend ClassMethods
  end

  module ClassMethods
    def bound_to_context
      belongs_to :context
      validates_presence_of :context
      class_eval { extend ContextRecord::SingletonMethods }
      include InstanceMethods
      class << self
        alias_method :original_find, :find
        alias_method :find, :find_with_context_scope
      end
    end
  end

  module SingletonMethods
    def without_context_scope
      begin
        Thread.current[:bypass_context_scope] = true
        result = yield
      ensure
        Thread.current[:bypass_context_scope] = nil
      end
      return result
    end

    def bypass_context_scope?
      Thread.current[:bypass_context_scope] == true
    end

    def find(*args)
      unless bypass_context_scope?
        with_scope(:find => {:conditions => "..."}) do
          return without_context_scope {original_find(*args)}
        end
      end
      original_find(*args)
    end

    def find_context_neutral(*args)
      without_context_scope {original_find(*args)}
    end
  end

  module InstanceMethods
    # ...
  end
end
ActiveRecord::Base.class_eval { include ContextRecord }

class SomeModel << ActiveRecord::Base
    bound_to_context
end
------------------------------------

Thanks
Dim
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-02-07 20:41
(Received via mailing list)
On Feb 7, 2006, at 3:10 AM, Dim wrote:

> but it's not RecordWithFeatures, but it's children, that should belong
> to "context".

Use the Class#inherited hook:

$ ruby -w
class X
def self.inherited(klass)
puts "Inherited by #{klass}"
end
end
class Y < X; end
Inherited by Y

So I think you would have:

def self.inherited(klass)
   klass.belongs_to :context
   klass.validates_presense_of :context
end

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-02-07 20:45
(Received via mailing list)
On Feb 7, 2006, at 3:54 AM, Byron Saltysiak wrote:

> A nice construct in Ruby for sharing code between classes but
> without the messyness of requiring a common ancestor is the mixin
> pattern. (http://www.rubycentral.com/book/tut_modules.html)

But he *has* a common ancestor, so he doesn't need mixins.

> If there are elements that you'd like to share you can make it more
> clear what is going on via something like the the following. The
> only thing is I'm not 100% sure it will work with the belongs_to
> and validates_presence_of - let me know!

... maybe you should test it?  Otherwise this just adds noise.

>   include ContextRecord
> end

This is a violation of DRY.  Don't do that.  Use subclassing as Dahl
and Nygaard intended.

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
This topic is locked and can not be replied to.