1

I'm trying to do something like this, where the send method's block has block arguments:

  klass.attribute_names.each do |attribute|
    klass.send(define_method("next_by_" + attribute) { object, user = nil
      if user
        klass.joins(:users).where("#{attribute} > ?", object.send(attribute)).where(users: {id: user.id}).first
      else
        klass.where("#{attribute} > ?", object.send(attribute)).first
      end
    })
  end

Which isn't valid Ruby. Is this possible to do, and if so what is the best way to do so?

EDIT: I realize the reserved keyword "class" here. It is simply for illustration purposes. I've changed it to klass to prevent further confusion.

More detail, this is the entire file:

module Helpers::ResourceRecordHelper

  # Remove this and call upon the module's methods to include instead to fix performance hit
  def self.included(source)
    define_class_methods(source)
  end

  def define_class_methods(source)
    source.attribute_names.each do |attribute|

      # These defined methods never reach the class
      define_method("next_by_" + attribute) { object, user = nil
        if user
          self.class.joins(:users).where("#{attribute} > ?", object.send(attribute)).where(users: {id: user.id}).first
        else
          self.class.where("#{attribute} > ?", object.send(attribute)).first
        end
      }

      define_method("previous_by_" + attribute) do object, user = nil
        if user
          self.class.joins(:users).where("#{attribute} < ?", object.send(attribute)).where(users: {id: user.id}).last
        else
          self.class.where("#{attribute} < ?", object.send(attribute)).last
        end
      end
    end

    def source.next(object, user = nil)
      source.next_by_id(object, user)
    end

    def source.previous(object, user = nil)
      source.previous_by_id(object, user)
    end
  end

  extend self

end
2
  • I think if you don't use the reserved word class this should work. Although it's a little tricky to understand, since you'd have to know that define_method returns a symbol that can be passed to send. Commented Mar 29, 2017 at 6:16
  • I believe it would be more typical to define the methods in one iteration and call them (using send) in another. Commented Mar 29, 2017 at 6:43

1 Answer 1

1

You have few issues, like using reserved words, not understanding the self (or rather not implementing your understanding correctly).

source.attribute_names.each do |attribute|
  source.singleton_class.instance_eval do
    define_method('next_by_' + attribute) do |object, user = nil|
      if user
        source.joins(:users)
              .where("#{attribute} > ?", object.public_send(attribute))
              .where(users: { id: user.id })
              .first
      else
        source.where("#{attribute} > ?", object.public_send(attribute)).first
      end
    end
  end
end
Sign up to request clarification or add additional context in comments.

5 Comments

Unfortunately this is not in a model, it's part of a module itself. Using define_method on the module seems to just define a method on the module, but I have an include statement as well. I'll post the entire module for clarification.
@DrakkenSaer you should have done this from the very beginning, because what I posted is a valid answer to your problem as it is now. And editing the question will invalidate the answer.
No... my question has nothing to do with models.
@DrakkenSaer if you're passing source to the method, making my code working is as easy as prepending source. to where it needs to be - that's it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.