makandra / active_type

Make any Ruby object quack like ActiveRecord
MIT License
1.09k stars 74 forks source link

Attribute not being overridden when method is defined inside an ActiveSupport::Concern #118

Closed aar0nr closed 3 years ago

aar0nr commented 4 years ago

Given the following code...

module Serviceable
  extend ActiveSupport::Concern

  included do
    attribute :services, :string
  end

  def services
    raise "foo"
  end
end
class Project < ActiveType::Object
  include Serviceable
end

I would expect calling the services method to raise an exception, but it returns nil.

Project.new.services
# => nil

Rewritten to use ActiveModel, it works as expected.

class Project
  include ActiveModel::Model
  include ActiveModel::Attributes
  include Serviceable
end
Project.new.services
# => RuntimeError (foo)

However, it works with ActiveType when not using a module:

class Project < ActiveType::Object
  attribute :services, :string

  def services
    raise "foo"
  end
end
Project.new.services
# => RuntimeError (foo)

Any ideas?

Versions:

rails (6.0.2.2)
active_type (1.3.1)
kratob commented 4 years ago

When using ActiveSupport::Concern, both the overridden services method as well as the method defined by attribute are defined in modules that just happen to be included in the "wrong" order.

I think we build the anonymous module for the attribute methods lazily. Maybe a fix could be to build and include it as soon as possible.

triskweline commented 4 years ago

As a workaround you might consider using a Modularity trait instead of ActiveSupport::Concern:

module Serviceable
  as_trait do
    attribute :services, :string

    def services
      raise "foo"
    end
  end  
end

class Project < ActiveType::Object
  include Serviceable
end

Project.new.services # raises "foo"
aar0nr commented 4 years ago

Thanks for the reply guys. Here is a more simple example without using a concern.

module Serviceable
  def services
    raise "foo"
  end
end
class Project < ActiveType::Object
  include Serviceable
  attribute :services, :string
end
Project.new.services
# => nil

Now we try including the module after the attribute, and it works as expected...

class Project < ActiveType::Object
  attribute :services, :string
  include Serviceable
end
Project.new.services
# => RuntimeError (foo)
aar0nr commented 3 years ago

When using ActiveSupport::Concern, both the overridden services method as well as the method defined by attribute are defined in modules that just happen to be included in the "wrong" order.

I think we build the anonymous module for the attribute methods lazily. Maybe a fix could be to build and include it as soon as possible.

@kratob Hello again. Yes, I think it would be good to mirror the behavior of Rails. I just spent some time slamming my head against my keyboard until I remembered why this wasn't working as expected. Doh! 😄

kratob commented 3 years ago

It turns out to be quite difficult to fix it. It is certainly doable (since ActiveRecord does it), but we did not find a simple consistent way to do it.

We would accept a PR that fixes this in a clean way, but will not try to fix it ourselves.

aar0nr commented 3 years ago

@kratob I understand, thanks for investigating.