PETER BROWN
PETER BROWN

Reputation: 550

Dynamically created modules in ruby

I'm experimenting with a pattern I'd like feedback on:

module Concerns
  def AuthenticatedS3Concern(options)
    AuthenticatedS3ConcernHelper.go(options)
  end

  module_function :AuthenticatedS3Concern

  module AuthenticatedS3ConcernHelper
    def self.go(options = {:attribute => :photo})
      @@auth_attr = options[:attribute] # the photo clip reference 
      @@auth_attr_url = "#{@@auth_attr}_authenticated_url" # set this to do a one time download

      Module.new do
        def self.included(base)
          base.send :include, AuthenticatedS3ConcernHelper::InstanceMethods
        end    

        class_eval %(
          def #{@@auth_attr}_authenticated_url(time_limit = 7.days)
            authenticated_url_for('#{@@auth_attr}', time_limit)
          end
        )
      end
    end

    module InstanceMethods    
      def authenticated_url_for(attached_file, time_limit) 
        AWS::S3::S3Object.url_for(self.send(attached_file).path('original'), self.send(attached_file).bucket_name, :expires_in => time_limit)
      end
    end    
  end
end

Which can be used like so:

require 'concerns/authenticated_s3_concern'
require 'concerns/remote_file_concern'

class Attachment
  include Concerns.AuthenticatedS3Concern(:attribute => :attachment) 
end

I'm curious if this is a good approach or a bad approach or what. Is there a better way to accomplish this kind of variably defined module stuff?

Thanks

Upvotes: 1

Views: 1718

Answers (2)

dnch
dnch

Reputation: 9605

Aside from making your maintenance developers brains hurt, I don't see any advantage to doing this.

From what I can understand, all this code does is create an instance method in the including class called attribute_name_authenticated_url – which is simply a wrapper for authenticated_url_for.

You could have easily done the same thing using method_missing or defining and calling a class method that creates your instance method. IMO, this approach is much simpler and readable:

module Concerns
  module AuthenticatedS3
    def authenticated_url_for(attached_file, time_limit = 7.days) 
      AWS::S3::S3Object.url_for(self.send(attached_file).path('original'), self.send(attached_file).bucket_name, :expires_in => time_limit)
    end    
  end
end

class Attachment
  include Concerns::AuthenticatedS3
end

@attachment = Attachment.new
@attachment.authenticated_url_for(:attribute_name)

Metaprogramming techniques are best when they don't get in the way of what you're trying to do.

Upvotes: 1

Mladen Jablanović
Mladen Jablanović

Reputation: 44110

Not sure why do you need modules at all.

If all you need to do is dynamically add a dynamically named method, you can start with:

def make_me_a_method meth
  define_method(meth){|param=7|
    puts param
  }
end

class C
  make_me_a_method :foo
end

C.new.foo(3)
#=> 3

Upvotes: 0

Related Questions