Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveInteraction::Base leaks _validators #174

Conversation

aaronjensen
Copy link

Some frameworks, like client_side_validations make use of ._validators. Because of the way ActiveInteraction::Base includes ActiveModel::Validations (which differs from the way that ActiveModel::Model does it) only one _validators method and associated class attribute is defined so they all share it. This causes problems.

The below test fails:

  it 'does not leak _validators' do
    first = Class.new(ActiveInteraction::Base) do
      string :foo
      validates_presence_of :foo
    end

    second = Class.new(ActiveInteraction::Base) do
      string :bar
      validates_presence_of :bar
    end

    expect(first._validators.keys).to eql [:foo]
    expect(second._validators.keys).to eql [:bar]
  end

I attempted to move includes to callbacks but this broke a couple tests.

In ActiveInteraction::Base

      def inherited(klass)
        klass.send :include, ActiveModelable
        klass.send :include, Runnable
        klass.send :validate, :input_errors
        klass.instance_variable_set(:@_interaction_filters, filters.dup)
      end

In ActiveModelable

    extend ActiveSupport::Concern
    included do #:nodoc:
      extend  ActiveModel::Naming
      include ActiveModel::Validations
      include ActiveModel::Conversion
    end

Failures:

Failures:

  1) ActiveInteraction::Base#compose with invalid composition has the correct errors
     Failure/Error: expect(outcome.errors[:base])
       expected collection contained:  ["X is required", "Y is required"]
       actual collection contained:    ["X translation missing: en.activemodel.errors.models.9f3d193a8a7083ca65b726895b1dfade.attributes.x.missing", "Y translation missing: en.activemodel.errors.models.9f3d193a8a7083ca65b726895b1dfade.attributes.y.missing"]
       the missing elements were:      ["X is required", "Y is required"]
       the extra elements were:        ["X translation missing: en.activemodel.errors.models.9f3d193a8a7083ca65b726895b1dfade.attributes.x.missing", "Y translation missing: en.activemodel.errors.models.9f3d193a8a7083ca65b726895b1dfade.attributes.y.missing"]
     # ./spec/active_interaction/base_spec.rb:317:in `block (4 levels) in <top (required)>'

  2) ActiveInteraction::Base#execute raises an error
     Failure/Error: expect { interaction.execute }.to raise_error NotImplementedError
       expected NotImplementedError, got #<NoMethodError: undefined method `execute' for #<ActiveInteraction::Base:0x007f8363d0f500>> with backtrace:
         # ./spec/active_interaction/base_spec.rb:325:in `block (4 levels) in <top (required)>'
         # ./spec/active_interaction/base_spec.rb:325:in `block (3 levels) in <top (required)>'
     # ./spec/active_interaction/base_spec.rb:325:in `block (3 levels) in <top (required)>'

@smdern
Copy link

smdern commented Apr 29, 2014

I've ran into the same issue.

@tfausak tfausak added the bug label Apr 29, 2014
@aaronjensen
Copy link
Author

I updated this with the actual changes I made to make it easier to see the bug.

@tfausak
Copy link
Collaborator

tfausak commented Apr 30, 2014

Thanks for finding this bug! I'd like to have a test case that doesn't use the "private" accessor _validators. This doesn't have exactly the same semantics, but it gets the point across:

class A < ActiveInteraction::Base
  string :a
  validates_presence_of :a
end

class B < ActiveInteraction::Base
  string :b
  validates_presence_of :b
end

A.validators == B.validators
# => true

I'll look at your proposed fix and see if I can figure out what's going on with the tests.

@tfausak
Copy link
Collaborator

tfausak commented Apr 30, 2014

Here's the test case version of my code snippet:

it 'does not leak validators' do
  a = Class.new(ActiveInteraction::Base) do
    string :a
    validates_presence_of :a
  end

  b = Class.new(ActiveInteraction::Base) do
    string :b
    validates_presence_of :b
  end

  expect(a.validators).to_not eq b.validators
end

And here's a minimal change that makes the test pass without affecting any of the other tests:

diff --git a/lib/active_interaction/base.rb b/lib/active_interaction/base.rb
index edcdb60..00f480d 100644
--- a/lib/active_interaction/base.rb
+++ b/lib/active_interaction/base.rb
@@ -134,6 +134,7 @@ module ActiveInteraction

       # @param klass [Class]
       def inherited(klass)
+        klass._validators = _validators.dup if self == Base
         klass.instance_variable_set(:@_interaction_filters, filters.dup)
       end

Does that seem like a reasonable fix?

Edit: That might have to be a deep dup, like Rails does. But _validators on Base should be empty anyway.

tfausak added a commit that referenced this pull request Apr 30, 2014
@tfausak tfausak mentioned this pull request Apr 30, 2014
@tfausak
Copy link
Collaborator

tfausak commented Apr 30, 2014

The fix turned out to be even simpler! Check out #176.

@tfausak tfausak self-assigned this Apr 30, 2014
@tfausak tfausak closed this in #176 Apr 30, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants