Skip to content

Commit

Permalink
Attributes.mass_assignment_strict_mode (#32)
Browse files Browse the repository at this point in the history
Implements mass_assignment_strict_mode

The setting is per class and configures strict handling of unknown attributes by raising an exception, just like Rails/ActiveModel does.

By default it is disabled, and you need to opt-in.

This is valuable for cases where Granite is used in combination with GQL mutations and there is no risk of using incorrect attributes or getting some accidental junk from web forms.
  • Loading branch information
paneq authored Nov 4, 2024
1 parent 7507675 commit 33606cc
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 2 deletions.
13 changes: 11 additions & 2 deletions lib/granite/form/model/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ module Attributes
extend ActiveSupport::Concern

included do
class_attribute :_attributes, :_attribute_aliases, :_sanitize, instance_reader: false, instance_writer: false
class_attribute :_attributes, :_attribute_aliases, :_sanitize,
:mass_assignment_strict_mode,
instance_reader: false, instance_writer: false
self._attributes = {}
self._attribute_aliases = {}
self._sanitize = true
self.mass_assignment_strict_mode = false

delegate :attribute_names, :has_attribute?, to: 'self.class'

Expand Down Expand Up @@ -180,7 +183,7 @@ def assign_attributes(attrs)
public_send("#{name}=", value)
else
attribute_type = sanitize_value ? 'primary' : 'undefined'
logger.debug("Ignoring #{attribute_type} `#{name}` attribute value for #{self} during mass-assignment")
report_unknown_attribute(attribute_type, name)
end
end
end
Expand Down Expand Up @@ -208,6 +211,12 @@ def initialize_copy(_)

private

def report_unknown_attribute(attribute_type, name)
raise ActiveModel::UnknownAttributeError.new(self, name.to_s) if self.class.mass_assignment_strict_mode

logger.debug("Ignoring #{attribute_type} `#{name}` attribute value for #{self} during mass-assignment")
end

def attributes_for_inspect
attribute_names(false).map do |name|
prefix = self.class.primary_name == name ? '*' : ''
Expand Down
21 changes: 21 additions & 0 deletions spec/granite/form/model/attributes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,27 @@ def assign_attributes(attrs)
end
end
end

context 'with mass_assignment_strict_mode' do
let(:model) do
stub_model do
self.mass_assignment_strict_mode = true
attribute :full_name, String
alias_attribute :name, :full_name
end
end

specify do
expect do
subject.assign_attributes(name: 'name', unexisting: 'value')
end.to raise_error(ActiveModel::UnknownAttributeError, /unknown attribute 'unexisting' for/)
end

specify do
subject.assign_attributes(name: 'name', full_name: 'full_name')
expect(subject.name).to eq('full_name')
end
end
end

describe '#sync_attributes' do
Expand Down

0 comments on commit 33606cc

Please sign in to comment.