Skip to content

Commit 8423724

Browse files
authored
Merge pull request #463 from AlexGascon/master
Raising UnknownAttribute when adding an attribute not in the model
2 parents 0a5ad32 + 243b7b3 commit 8423724

File tree

8 files changed

+125
-0
lines changed

8 files changed

+125
-0
lines changed

lib/dynamoid/errors.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,7 @@ def initialize(document)
7575
class InvalidQuery < Error; end
7676

7777
class UnsupportedKeyType < Error; end
78+
79+
class UnknownAttribute < Error; end
7880
end
7981
end

lib/dynamoid/fields.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,10 @@ def warn_about_method_overriding(method_name, field_name)
309309
def write_attribute(name, value)
310310
name = name.to_sym
311311

312+
unless attribute_is_present_on_model?(name)
313+
raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{name} is not part of the model")
314+
end
315+
312316
if association = @associations[name]
313317
association.reset
314318
end
@@ -405,5 +409,10 @@ def set_inheritance_field
405409
send("#{type}=", self.class.name)
406410
end
407411
end
412+
413+
def attribute_is_present_on_model?(attribute_name)
414+
setter = "#{attribute_name}=".to_sym
415+
respond_to?(setter)
416+
end
408417
end
409418
end

lib/dynamoid/persistence.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'dynamoid/persistence/update_fields'
99
require 'dynamoid/persistence/upsert'
1010
require 'dynamoid/persistence/save'
11+
require 'dynamoid/persistence/update_validations'
1112

1213
# encoding: utf-8
1314
module Dynamoid
@@ -276,6 +277,9 @@ def update!(hash_key, range_key_value = nil, attrs)
276277
# +update_fields+ uses the +UpdateItem+ operation so it saves changes and
277278
# loads an updated document back with one HTTP request.
278279
#
280+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
281+
# attributes is not on the model
282+
#
279283
# @param hash_key_value [Scalar value] hash key
280284
# @param range_key_value [Scalar value] range key (optional)
281285
# @param attrs [Hash]
@@ -324,6 +328,9 @@ def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions
324328
# +upsert+ uses the +UpdateItem+ operation so it saves changes and loads
325329
# an updated document back with one HTTP request.
326330
#
331+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
332+
# attributes is not on the model
333+
#
327334
# @param hash_key_value [Scalar value] hash key
328335
# @param range_key_value [Scalar value] range key (optional)
329336
# @param attrs [Hash]
@@ -491,6 +498,9 @@ def save(options = {})
491498
#
492499
# user.update_attributes(age: 27, last_name: 'Tylor')
493500
#
501+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
502+
# attributes is not on the model
503+
#
494504
# @param attributes [Hash] a hash of attributes to update
495505
# @return [true|false] Whether updating successful or not
496506
# @since 0.2.0
@@ -507,6 +517,9 @@ def update_attributes(attributes)
507517
# Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
508518
# fails.
509519
#
520+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
521+
# attributes is not on the model
522+
#
510523
# @param attributes [Hash] a hash of attributes to update
511524
def update_attributes!(attributes)
512525
attributes.each { |attribute, value| write_attribute(attribute, value) }
@@ -519,6 +532,9 @@ def update_attributes!(attributes)
519532
#
520533
# user.update_attribute(:last_name, 'Tylor')
521534
#
535+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
536+
# attributes is not on the model
537+
#
522538
# @param attribute [Symbol] attribute name to update
523539
# @param value [Object] the value to assign it
524540
# @return [Dynamoid::Document] self

lib/dynamoid/persistence/update_fields.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def initialize(model_class, partition_key:, sort_key:, attributes:, conditions:)
1717
end
1818

1919
def call
20+
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
21+
2022
if Dynamoid::Config.timestamps
2123
@attributes[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
2224
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
module Dynamoid
4+
module Persistence
5+
# @private
6+
module UpdateValidations
7+
def self.validate_attributes_exist(model_class, attributes)
8+
model_attributes = model_class.attributes.keys
9+
10+
attributes.each do |attr_name, _|
11+
unless model_attributes.include?(attr_name)
12+
raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{attr_name} does not exist in #{model_class}")
13+
end
14+
end
15+
end
16+
end
17+
end
18+
end

lib/dynamoid/persistence/upsert.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def initialize(model_class, partition_key:, sort_key:, attributes:, conditions:)
1717
end
1818

1919
def call
20+
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
21+
2022
if Dynamoid::Config.timestamps
2123
@attributes[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
2224
end

spec/dynamoid/fields_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,5 +381,13 @@ def name=(str)
381381
expect(obj.attributes[:count]).to eql(101)
382382
end
383383
end
384+
385+
it 'raises an UnknownAttribute error if the attribute is not on the model' do
386+
obj = new_class.new
387+
388+
expect {
389+
obj.write_attribute(:name, 'Alex')
390+
}.to raise_error Dynamoid::Errors::UnknownAttribute
391+
end
384392
end
385393
end

spec/dynamoid/persistence_spec.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,17 @@ def log_message
928928
end.to change { d.reload.name }.to('[Updated]')
929929
end
930930

931+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
932+
klass = new_class do
933+
field :name
934+
end
935+
936+
obj = klass.create(name: 'Alex')
937+
expect {
938+
klass.update!(obj.id, age: 26)
939+
}.to raise_error Dynamoid::Errors::UnknownAttribute
940+
end
941+
931942
describe 'timestamps' do
932943
it 'sets updated_at if Config.timestamps=true', config: { timestamps: true } do
933944
d = document_class.create(name: 'Document#1')
@@ -1047,6 +1058,18 @@ def log_message
10471058
end.to change { d.reload.name }.to('[Updated]')
10481059
end
10491060

1061+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
1062+
klass = new_class do
1063+
field :name
1064+
end
1065+
1066+
obj = klass.create(name: 'Alex')
1067+
1068+
expect do
1069+
klass.update(obj.id, name: 'New name', age: 26)
1070+
end.to raise_error Dynamoid::Errors::UnknownAttribute
1071+
end
1072+
10501073
describe 'timestamps' do
10511074
it 'sets updated_at if Config.timestamps=true', config: { timestamps: true } do
10521075
d = document_class.create(name: 'Document#1')
@@ -1278,6 +1301,14 @@ def log_message
12781301
expect(klass.find(a.id)[:hash]).to eql('1': 'b')
12791302
end
12801303
end
1304+
1305+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
1306+
obj = document_class.create(title: 'New Document')
1307+
1308+
expect {
1309+
document_class.update_fields(obj.id, { title: 'New title', publisher: 'New publisher' } )
1310+
}.to raise_error Dynamoid::Errors::UnknownAttribute
1311+
end
12811312
end
12821313

12831314
describe '.upsert' do
@@ -1455,6 +1486,14 @@ def log_message
14551486
expect(klass.find(a.id)[:hash]).to eql('1': 'b')
14561487
end
14571488
end
1489+
1490+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
1491+
obj = document_class.create(title: 'New Document')
1492+
1493+
expect {
1494+
document_class.upsert(obj.id, { title: 'New title', publisher: 'New publisher' } )
1495+
}.to raise_error Dynamoid::Errors::UnknownAttribute
1496+
end
14581497
end
14591498

14601499
describe '.inc' do
@@ -1977,6 +2016,19 @@ def log_message
19772016
end.not_to raise_error
19782017
end
19792018
end
2019+
2020+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
2021+
klass = new_class do
2022+
field :age, :integer
2023+
field :name, :string
2024+
end
2025+
2026+
obj = klass.create!(name: 'Alex', age: 26)
2027+
2028+
expect {
2029+
obj.update_attribute(:city, 'Dublin')
2030+
}.to raise_error(Dynamoid::Errors::UnknownAttribute)
2031+
end
19802032
end
19812033

19822034
describe '#update_attributes' do
@@ -2068,6 +2120,14 @@ def log_message
20682120
end.not_to raise_error
20692121
end
20702122
end
2123+
2124+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
2125+
obj = klass.create!(name: 'Alex', age: 26)
2126+
2127+
expect {
2128+
obj.update_attributes!(city: 'Dublin', age: 27)
2129+
}.to raise_error(Dynamoid::Errors::UnknownAttribute)
2130+
end
20712131
end
20722132

20732133
describe '#update_attributes!' do
@@ -2110,6 +2170,14 @@ def log_message
21102170
expect(klass.find(obj.id).age).to eql 26
21112171
end
21122172

2173+
it 'raises an UnknownAttribute error when adding an attribute that is not on the model' do
2174+
obj = klass.create!(name: 'Alex', age: 26)
2175+
2176+
expect {
2177+
obj.update_attributes!(city: 'Dublin', age: 27)
2178+
}.to raise_error(Dynamoid::Errors::UnknownAttribute)
2179+
end
2180+
21132181
describe 'type casting' do
21142182
it 'type casts attributes' do
21152183
klass = new_class do

0 commit comments

Comments
 (0)