Ruby, Rails & Inheritance


#1

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


#2

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

#3

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


#4

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 H. - removed_email_address@domain.invalid - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#5

On Feb 7, 2006, at 3:54 AM, Byron S. 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 H. - removed_email_address@domain.invalid - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com