2

I have method in my rails model which returns anonymous class:

def today_earnings
Class.new do
  def initialize(user)
    @user = user
  end

  def all
    @user.store_credits.where(created_at: Time.current.beginning_of_day..Time.current.end_of_day)
  end

  def unused
    all.map { |el| el.amount - el.amount_used }.instance_eval do
      def to_f
        reduce(:+)
      end
    end
  end

  def used
    all.map(&:amount_used).instance_eval do
      def to_f
        reduce(:+)
      end
    end
  end
end.new(self)

end

I want to achieve possibility to chain result in that way user.today_earning.unused.to_f and I have some problems with instance eval because when I call to_f on result it's undefined method, I guess it is due to ruby copying returned value so the instance gets changed, is it true? And if I'm correct how can I change the code to make it work. Also I'm wondering if making new model can be better solution than anomyous class thus I need advice if anonymous class is elegant in that case and if so how can I add to_f method to returned values

2
  • I don't think you need this complexity. A new model would probably be cleaner. Also, you don't calculate or return anything in instance_eval, you only define to_f, so you then try to call the method to_f on the :to_f Symbol. You might try to add self at the end of the instance_eval block but it feels like a hack. Commented Feb 13, 2020 at 12:09
  • 1
    I assume that you have a store_credits table with amount and amount_used columns? If so, the obvious solution would be a database query for getting the results. Commented Feb 13, 2020 at 15:43

2 Answers 2

1

Yes, Anonymous class makes the code much complex. I would suggest a seperate class. It will solve 2 problems here.

  1. defining some anonymous class again and again when we call the today_earnings method.
  2. Readability of the code.

Now coming to actual question, you can try something similar to hash_with_indifferent_access. The code looks as follows.

class NumericArray < Array
   def to_f
     reduce(:+)
   end
end

Array.class_eval do
   def with_numeric_operations
      NumericArray.new(self)
   end
end

Usage will be:

Class Earnings
  def initialize(user)
    @user = user
  end

  def all
    @user.store_credits.where(created_at: Time.current.beginning_of_day..Time.current.end_of_day)
  end

  def unused
    all.map { |el| el.amount - el.amount_used }.with_numeric_operations
  end

  def used
    all.map(&:amount_used).with_numeric_operations
  end
end
Sign up to request clarification or add additional context in comments.

1 Comment

Subclassing Array or any of the other classes in the core (String, Numeric etc.) is a bad idea. They are written in C and do not behave like normal ruby classes. Better would be to use SimpleDelegator and wrap the array. words.steveklabnik.com/beware-subclassing-ruby-core-classes
1

This looks like a "clever" but ridiculously over-complicated way to do something that can be simply and efficiently done in the database.

User.joins(:store_credits)
    .select(
       'users.*',
       'SUM(store_credits.amount_used) AS amount_used',
       'SUM(store_credits.amount) - amount_used AS unused',
    )
    .where(store_credits: { created_at: Time.current.beginning_of_day..Time.current.end_of_day })
    .group(:id)

Comments

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.