Skip to content

Commit

Permalink
Define methods on dynamic module
Browse files Browse the repository at this point in the history
Currently you cannot override the generated methods and access super ie:

```ruby
monetize :price_cents

def price=(value)
   # stuff
   super # NoMethodError
end
```

This happens because the method is defined on the class directly so their is no super method to call. A solution today is to create an alias (`alias :old_price= :price=`) before overriding the method as is recommending in issue #507.

Instead, a new module can be created, included into the class, and the methods defined on it allowing `super` to work as expected.
  • Loading branch information
jbennett committed Jul 3, 2024
1 parent df7a030 commit 9030ec3
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 14 deletions.
38 changes: 24 additions & 14 deletions lib/money-rails/active_record/monetizable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class ReadOnlyCurrencyException < MoneyRails::Error; end
extend ActiveSupport::Concern

module ClassMethods
MODULE_NAME = :DynamicMoneyAttributes

def monetized_attributes
monetized_attributes = @monetized_attributes || {}.with_indifferent_access

Expand Down Expand Up @@ -116,23 +118,31 @@ def monetize(*fields)
end
end


# Getter for monetized attribute
define_method name do |*args, **kwargs|
read_monetized name, subunit_name, options, *args, **kwargs
if const_defined?(MODULE_NAME, false)
mod = const_get(MODULE_NAME)
else
mod = const_set(MODULE_NAME, Module.new)
include mod
end

# Setter for monetized attribute
define_method "#{name}=" do |value|
write_monetized name, subunit_name, value, validation_enabled, instance_currency_name, options
end
mod.module_eval do
# Getter for monetized attribute
define_method name do |*args, **kwargs|
read_monetized name, subunit_name, options, *args, **kwargs
end

# Setter for monetized attribute
define_method "#{name}=" do |value|
write_monetized name, subunit_name, value, validation_enabled, instance_currency_name, options
end

if validation_enabled
# Ensure that the before_type_cast value is cleared when setting
# the subunit value directly
define_method "#{subunit_name}=" do |value|
instance_variable_set "@#{name}_money_before_type_cast", nil
write_attribute(subunit_name, value)
if validation_enabled
# Ensure that the before_type_cast value is cleared when setting
# the subunit value directly
define_method "#{subunit_name}=" do |value|
instance_variable_set "@#{name}_money_before_type_cast", nil
write_attribute(subunit_name, value)
end
end
end

Expand Down
17 changes: 17 additions & 0 deletions spec/active_record/monetizable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ def update_product(*attributes)

context ".monetized_attributes" do

it "allows adds methods to the inheritance chain" do
class MyProduct < ActiveRecord::Base
self.table_name = :products
monetize :price_cents
attr_reader :side_effect

def price=(value)
@side_effect = true
super
end
end

p = MyProduct.new(price: 10)
expect(p.price).to eq Money.new(10_00)
expect(p.side_effect).to be_truthy
end

class InheritedMonetizeProduct < Product
monetize :special_price_cents
end
Expand Down

0 comments on commit 9030ec3

Please sign in to comment.