Skip to content

Ensure fields don't get overridden by default #97

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/subroutine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@
require "subroutine/fields"
require "subroutine/op"

require "logger"

module Subroutine

def self.logger
@logger
end

def self.logger=(logger)
@logger = logger
end

# Used by polymorphic association fields to resolve the class name to a ruby class
def self.constantize_polymorphic_class_name(class_name)
return @constantize_polymorphic_class_name.call(class_name) if defined?(@constantize_polymorphic_class_name)
Expand All @@ -38,6 +48,18 @@ def self.include_defaults_in_params?
false
end

def self.field_redefinition_behavior
@field_redefinition_behavior ||= :warn
end

def self.field_redefinition_behavior=(symbol)
symbol = symbol.to_sym
possible = %i[error warn ignore]
raise ArgumentError, "#{symbol} must be one of #{possible.inspect}" unless possible.include?(symbol)

@field_redefinition_behavior = symbol
end

def self.inheritable_field_options=(opts)
@inheritable_field_options = opts.map(&:to_sym)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/subroutine/association_fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def field_with_association(field_name, options = {})
if options[:type]&.to_sym == :association
config = ::Subroutine::AssociationFields::Configuration.new(field_name, options)

field_without_association(config.as, config)

if config.polymorphic?
field config.foreign_type_method, config.build_foreign_type_field
else
Expand All @@ -77,8 +79,6 @@ def field_with_association(field_name, options = {})
end

field config.foreign_key_method, config.build_foreign_key_field

field_without_association(config.as, config)
else
field_without_association(field_name, options)
end
Expand Down
1 change: 1 addition & 0 deletions lib/subroutine/association_fields/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def build_child_field(name, opts = {})
child_opts = inheritable_options
child_opts.merge!(opts)
child_opts[:association_name] = as
child_opts[:allow_override] = true
ComponentConfiguration.new(name, child_opts)
end

Expand Down
12 changes: 12 additions & 0 deletions lib/subroutine/fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
module Subroutine
module Fields

DuplicateFieldError = Class.new(StandardError)

extend ActiveSupport::Concern

def self.allowed_input_classes
Expand All @@ -30,6 +32,16 @@ def self.action_controller_params_loaded?
module ClassMethods

def field(field_name, options = {})

if field_configurations.key?(field_name.to_sym) && !options[:allow_override]
case Subroutine.field_redefinition_behavior
when :error
raise DuplicateFieldError, "[subroutine] #{self} redefined `#{field_name}`. Add `allow_override: true` to reconfigure the field."
when :warn
Subroutine.logger&.warn("[subroutine] #{self} redefines `#{field_name}`. Add `allow_override: true` to silence this warning.\nCalled from: #{caller.join("\n")}")
end
end

config = ::Subroutine::Fields::Configuration.from(field_name, options)
config.validate!

Expand Down
6 changes: 3 additions & 3 deletions lib/subroutine/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

module Subroutine

MAJOR = 4
MINOR = 5
MAJOR = 5
MINOR = 0
PATCH = 0
PRE = nil
PRE = "alpha2"

VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")

Expand Down
27 changes: 27 additions & 0 deletions test/subroutine/fields_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,32 @@ def test_group_fields_are_not_mutated_by_subclasses
assert_equal(%i[food], MutationChild.fields_by_group[:four_letter].sort)
end

def test_redefinition_of_field__error
Subroutine.stubs(:field_redefinition_behavior).returns(:error)
Subroutine.stubs(:logger).returns(mock)
Subroutine.logger.expects(:warn).never
assert_raises Subroutine::Fields::DuplicateFieldError do
Whatever.send(:field, :foo, type: :string, default: "foo")
end
end

def test_redefinition_of_field__warn
Subroutine.stubs(:field_redefinition_behavior).returns(:warn)
Subroutine.stubs(:logger).returns(mock)
Subroutine.logger.expects(:warn).with(includes("[subroutine] #{Whatever} redefines `foo`. Add `allow_override: true` to silence this warning.")).once
Whatever.send(:field, :foo, type: :string, default: "foo")
end

def test_redefinition_of_field__ignore
Subroutine.stubs(:field_redefinition_behavior).returns(:ignore)
Subroutine.stubs(:logger).returns(mock)
Subroutine.logger.expects(:warn).never

foo_config1 = Whatever.field_configurations[:foo]
Whatever.send(:field, :foo, type: :string, default: "foo")
foo_config2 = Whatever.field_configurations[:foo]
refute_equal foo_config1.object_id, foo_config2.object_id
end

end
end
4 changes: 2 additions & 2 deletions test/support/ops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class OnlyFooBarOp < ::Subroutine::Op

class InheritedDefaultsOp < ::DefaultsOp

field :bar, default: "barstool", allow_overwrite: true
field :bar, default: "barstool", allow_override: true

end

Expand Down Expand Up @@ -354,7 +354,7 @@ class SimpleAssociationWithStringIdOp < ::OpWithAssociation

class UnscopedSimpleAssociationOp < ::OpWithAssociation

association :user, unscoped: true, allow_overwrite: true
association :user, unscoped: true, allow_override: true

end

Expand Down