Skip to content

Instantly share code, notes, and snippets.

@wycats
Created June 11, 2010 18:16
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save wycats/434844 to your computer and use it in GitHub Desktop.
Save wycats/434844 to your computer and use it in GitHub Desktop.

If one was inclined to use the acts_as_yaffle pattern, they would probably use the second one, rather than the heavily cargo-culted first one.

module Yaffle
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
send :include, InstanceMethods
end
end
module InstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
ActiveRecord::Base.send :include, Yaffle
module Yaffle
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include InstanceMethods
end
module InstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
ActiveRecord::Base.extend(Yaffle)
module Yaffle
# since this is for internal consumption only, just extend directly;
# don't rewrite the include method to mean extend
def self.included(base)
# extend is a public method
base.send :extend, ClassMethods
end
# if we extended directly, we could get rid of the ClassMethods
# module and move the methods into the Yaffle module. You onluy
# ever need one of ClassMethods or InstanceMethods
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
# we're inside the class, so we don't need to use send to
# use a private method
send :include, InstanceMethods
end
end
module InstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
# If we'd used extend, we wouldn't need a send
ActiveRecord::Base.send :include, Yaffle
@raggi
Copy link

raggi commented Jun 11, 2010

Why not just put #squawk into module Yaffle?

@raggi
Copy link

raggi commented Jun 11, 2010

I don't really care which way around people do it, via extend or via send include. What bothers me more is if I see extra stuff on every both inheritance chains, it seems like waste.

@jamie
Copy link

jamie commented Jun 11, 2010

Additionally, if you don't require any options, don't use acts_as! A simple module will suffice.

@nono
Copy link

nono commented Jun 11, 2010

@raggi: so, you have the squawk method only in classes that use acts_as_yaffle, not directly in ActiveRecord::Base.

@raggi
Copy link

raggi commented Jun 11, 2010

IOW. The opposite of second.rb:

module Yaffle
  module BaseMethods
    def acts_as_yaffle(options = {})
      cattr_accessor :yaffle_text_field
      self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s

      # we're inside the class, so we don't need to use send to
      # use a private method
      send :include, Yaffle
    end
  end

  def squawk(string)
    write_attribute(self.class.yaffle_text_field, string.to_squawk)
  end
end

ActiveRecord::Base.extend, Yaffle::BaseMethods

@raggi
Copy link

raggi commented Jun 11, 2010

whoops, without the syntax error ofc.

@raggi
Copy link

raggi commented Jun 11, 2010

Or you could do away with the whole cult, and use no class-level-module where there's no need for extra methods. Some might consider this too magic, but unless you're putting the cattr_accessor inside the module definition itself, I don't think it makes any /real/ difference.

module Yaffle
  def self.included(base)
    base.cattr_accessor :yaffle_text_field
    field = (base.options[:yaffle_text_field] || :last_squawk).to_s
    base.yaffle_text_field = field
  end

  def squawk(string)
    write_attribute(self.class.yaffle_text_field, string.to_squawk)
  end
end

For the uninitiated, this means you can just use:

include Yaffle

Rather than acts_as_whatever.

@funny-falcon
Copy link

somewhere in ActiveSupport
class Module
def mixin(mod, opts={})
if mod.class_do
args = mod.default_args if args.empty?
class_exec *args, &mod.class_do
end
include mod.instance if mod.instance
end
end

module Mixin
  def class_do(*default_args, &block)
    if block
      @default_args = default_args
      @class_do = block
    end
    @class_do
  end
  attr_reader :default_args
  def instance(&block)
    @instance = Module.new(&block) if block
    @instance
  end
end

in a plugin
module Yaffle
extend Mixin

  class_do(:yaffle_text_field => :last_sqawk) do |options|
    cattr_accessor(:yaffle_text_field)
    self.yaffle_text_field = options[:yaffle_text_field].to_s  
  end

  instance do
    def squawk(string)
      write_attribute(yaffle_text_field, string.to_squawk)
    end
  end
end

in app
class A
mixin Yaffle
end

class B
  mixin Yaffle, :yaffle_text_field=>'hihi'
end

@raggi
Copy link

raggi commented Jun 12, 2010

:'(

@twifkak
Copy link

twifkak commented Jun 13, 2010

Alternatively (perhaps this is too magic):

module Yaffle
  def self.included(mod, options={})
    if mod.is_a? ActiveRecord::Base
      mod.cattr_accessor :yaffle_text_field
      mod.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
    end
  end

  def squawk(string)
    write_attribute(self.class.yaffle_text_field, string.to_squawk)
  end
end

def Yaffle(options={})
  Module.new do
    include Yaffle
    class << self; self end.send :define_method, :included do |mod|
      Yaffle.included mod, options
    end
  end
end

@raggi
Copy link

raggi commented Jun 14, 2010

wtf

@twifkak
Copy link

twifkak commented Jun 14, 2010

Yeah, I don't like the if statement, either. This fixes it:

def support_options(sym)
  eval <<-DEF
    def #{sym}(options={})
      m = #{sym}.dup
      class << m; self end.send(:define_method, :included) {|mod|}
      Module.new do
        include m
        class << self; self end.send :define_method, :included do |mod|
          #{sym}.included mod, options
        end
      end
    end
  DEF
end

module Yaffle
  def self.included(mod, options={})
    mod.cattr_accessor :yaffle_text_field
    mod.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
  end

  def squawk(string)
    write_attribute(self.class.yaffle_text_field, string.to_squawk)
  end
end

support_options :Yaffle

# Use like:
# include Yaffle # or
# include Yaffle(:a => :b)

Then again, if you didn't like the extra stuff on the inheritance chain, you definitely won't like that. :)

Probably best to just override include to call some other method if given more than one parameter.

@raggi
Copy link

raggi commented Jun 14, 2010

i was aiming for simpler, this code is insane.

@twifkak
Copy link

twifkak commented Jun 14, 2010

why, thank you! Anyway, here's the simpler concept to which I earlier alluded:

def include(mod, *args)
  mod.set_options self, *args if mod.respond_to? :set_options
  super mod
end

Obviously, that would need to be agreed upon somewhere. But

include Yaffle, :yaffle_text_field => "blah"

reads a lot better than

cattr_accessor :options
self.options = {:yaffle_text_field => "blah"}
include Yaffle

@raggi
Copy link

raggi commented Jun 14, 2010

with my last code, all you do is:

include Yaffle
self.yaffle_text_field = "blah"

If cattr_accessor was changed to allow for sets by parameter on the reader method, then this would read even better:

include Yaffle
yaffle_text_field "blah"

And can be implemented easily.

You seem to be unaware that:

class A; include B, C, D; end

is valid right now.

@twifkak
Copy link

twifkak commented Jun 14, 2010

Actually, your last code will raise a "NoMethodError: undefined method options". I get what you're saying, but in general, whatever initialization included() does might not be easily reversible.

I was unaware that Module#include took *args. Oh, well! I suppose one could check the type of the second arg, since I doubt anybody depends on include raising a TypeError.

@raggi
Copy link

raggi commented Jun 14, 2010

meh. two lines of code for users is fine. breaking stuff for the sake of replacing "\n" with ", :([^ ]+) => " is silly IMO

@raggi
Copy link

raggi commented Jun 14, 2010

anyone know how to /ignore further comments on a gist?

@twifkak
Copy link

twifkak commented Jun 15, 2010

Wow. It doesn't take a genius to realize that there are many places to ask that question that are more likely to garner an answer than in the comments of a gist that six people have posted to. You did that just to broadcast that you disagreed with and disapproved of me. Presumably, you thought that doing so would either shame me or inform others of your superiority. To me, it communicated your inability to handle dissent.

I read the anguish in your earlier comments as intolerance of whimsy. I take it you'll be celebrating whyday for a reason entirely different from most.

@raggi
Copy link

raggi commented Jun 15, 2010

i was only asking because i'm done with this.

oh, i see, you were trying to be "artistic"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment