mongoid 9.0.8 → 9.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1819,6 +1819,400 @@ describe Mongoid::Association::Referenced::HasMany::Enumerable do
1819
1819
  end
1820
1820
  end
1821
1821
 
1822
+ describe '#pluck' do
1823
+ let(:person) do
1824
+ Person.create!
1825
+ end
1826
+
1827
+ let!(:post) do
1828
+ Post.create!(person_id: person.id, title: 'Test Title')
1829
+ end
1830
+
1831
+ let(:base) { Person }
1832
+ let(:association) { Person.relations[:posts] }
1833
+
1834
+ let(:criteria) do
1835
+ Post.where(person_id: person.id)
1836
+ end
1837
+
1838
+ context 'when the enumerable is not loaded' do
1839
+ let!(:enumerable) do
1840
+ described_class.new(criteria, base, association)
1841
+ end
1842
+
1843
+ context 'when the criteria is present' do
1844
+ it 'delegates to the criteria pluck method' do
1845
+ result = enumerable.pluck(:title)
1846
+ expect(result).to eq(['Test Title'])
1847
+ end
1848
+
1849
+ context 'when added docs are present' do
1850
+ it 'combines the results from the criteria and the added docs' do
1851
+ added_post = Post.new(title: 'Added Title', person_id: person.id)
1852
+ enumerable << added_post
1853
+
1854
+ expect(criteria).to receive(:pluck).with(:title).and_return(['Test Title'])
1855
+ result = enumerable.pluck(:title)
1856
+ expect(result).to eq(['Test Title', 'Added Title'])
1857
+ end
1858
+ end
1859
+ end
1860
+
1861
+ context 'when the criteria is not present' do
1862
+ let(:enumerable) { described_class.new([], base, association) }
1863
+
1864
+ it 'returns nothing' do
1865
+ result = enumerable.pluck(:title)
1866
+ expect(result).to eq([])
1867
+ end
1868
+
1869
+ context 'when added docs are present' do
1870
+ it 'returns the values from the added docs' do
1871
+ added_post = Post.new(title: 'Added Title', person_id: person.id)
1872
+ enumerable << added_post
1873
+
1874
+ result = enumerable.pluck(:title)
1875
+ expect(result).to eq(['Added Title'])
1876
+ end
1877
+ end
1878
+ end
1879
+ end
1880
+
1881
+ context 'when the enumerable is loaded' do
1882
+ let(:enumerable) { described_class.new([post], base, association) }
1883
+
1884
+ it 'returns the values from the loaded documents' do
1885
+ result = enumerable.pluck(:title)
1886
+ expect(result).to eq(['Test Title'])
1887
+ end
1888
+
1889
+ context 'when added docs are present' do
1890
+ it 'returns the values from both loaded and added docs' do
1891
+ added_post = Post.new(title: 'Added Title', person_id: person.id)
1892
+ enumerable << added_post
1893
+
1894
+ result = enumerable.pluck(:title)
1895
+ expect(result).to eq(['Test Title', 'Added Title'])
1896
+ end
1897
+ end
1898
+ end
1899
+ end
1900
+
1901
+ describe '#pluck with aliases' do
1902
+ let!(:parent) do
1903
+ Company.create!
1904
+ end
1905
+
1906
+ context 'when the field is aliased' do
1907
+ let!(:expensive) do
1908
+ parent.products.create!(price: 100000)
1909
+ end
1910
+
1911
+ let!(:cheap) do
1912
+ parent.products.create!(price: 1)
1913
+ end
1914
+
1915
+ context 'when using alias_attribute' do
1916
+
1917
+ let(:plucked) do
1918
+ parent.products.pluck(:price)
1919
+ end
1920
+
1921
+ it 'uses the aliases' do
1922
+ expect(plucked).to eq([ 100000, 1 ])
1923
+ end
1924
+ end
1925
+ end
1926
+
1927
+ context 'when plucking a localized field' do
1928
+ with_default_i18n_configs
1929
+
1930
+ before do
1931
+ I18n.locale = :en
1932
+ p = parent.products.create!(name: 'english-text')
1933
+ I18n.locale = :de
1934
+ p.name = 'deutsch-text'
1935
+ p.save!
1936
+ end
1937
+
1938
+ context 'when plucking the entire field' do
1939
+ let(:plucked) do
1940
+ parent.products.all.pluck(:name)
1941
+ end
1942
+
1943
+ let(:plucked_translations) do
1944
+ parent.products.all.pluck(:name_translations)
1945
+ end
1946
+
1947
+ let(:plucked_translations_both) do
1948
+ parent.products.all.pluck(:name_translations, :name)
1949
+ end
1950
+
1951
+ it 'returns the demongoized translations' do
1952
+ expect(plucked.first).to eq('deutsch-text')
1953
+ end
1954
+
1955
+ it 'returns the full translations hash to _translations' do
1956
+ expect(plucked_translations.first).to eq({'de'=>'deutsch-text', 'en'=>'english-text'})
1957
+ end
1958
+
1959
+ it 'returns both' do
1960
+ expect(plucked_translations_both.first).to eq([{'de'=>'deutsch-text', 'en'=>'english-text'}, 'deutsch-text'])
1961
+ end
1962
+ end
1963
+
1964
+ context 'when plucking a specific locale' do
1965
+
1966
+ let(:plucked) do
1967
+ parent.products.all.pluck(:'name.de')
1968
+ end
1969
+
1970
+ it 'returns the specific translations' do
1971
+ expect(plucked.first).to eq('deutsch-text')
1972
+ end
1973
+ end
1974
+
1975
+ context 'when plucking a specific locale from _translations field' do
1976
+
1977
+ let(:plucked) do
1978
+ parent.products.all.pluck(:'name_translations.de')
1979
+ end
1980
+
1981
+ it 'returns the specific translations' do
1982
+ expect(plucked.first).to eq('deutsch-text')
1983
+ end
1984
+ end
1985
+
1986
+ context 'when fallbacks are enabled with a locale list' do
1987
+ require_fallbacks
1988
+
1989
+ before do
1990
+ I18n.fallbacks[:he] = [ :en ]
1991
+ end
1992
+
1993
+ let(:plucked) do
1994
+ parent.products.all.pluck(:name).first
1995
+ end
1996
+
1997
+ it 'correctly uses the fallback' do
1998
+ I18n.locale = :en
1999
+ parent.products.create!(name: 'english-text')
2000
+ I18n.locale = :he
2001
+ expect(plucked).to eq 'english-text'
2002
+ end
2003
+ end
2004
+
2005
+ context 'when the localized field is aliased' do
2006
+ before do
2007
+ I18n.locale = :en
2008
+ parent.products.delete_all
2009
+ p = parent.products.create!(name: 'ACME Rocket Skates', tagline: 'english-text')
2010
+ I18n.locale = :de
2011
+ p.tagline = 'deutsch-text'
2012
+ p.save!
2013
+ end
2014
+
2015
+ context 'when plucking the entire field' do
2016
+ let(:plucked) do
2017
+ parent.products.all.pluck(:tagline)
2018
+ end
2019
+
2020
+ let(:plucked_unaliased) do
2021
+ parent.products.all.pluck(:tl)
2022
+ end
2023
+
2024
+ let(:plucked_translations) do
2025
+ parent.products.all.pluck(:tagline_translations)
2026
+ end
2027
+
2028
+ let(:plucked_translations_both) do
2029
+ parent.products.all.pluck(:tagline_translations, :tagline)
2030
+ end
2031
+
2032
+ it 'returns the demongoized translations' do
2033
+ expect(plucked.first).to eq('deutsch-text')
2034
+ end
2035
+
2036
+ it 'returns the demongoized translations when unaliased' do
2037
+ expect(plucked_unaliased.first).to eq('deutsch-text')
2038
+ end
2039
+
2040
+ it 'returns the full translations hash to _translations' do
2041
+ expect(plucked_translations.first).to eq({ 'de' => 'deutsch-text', 'en' => 'english-text' })
2042
+ end
2043
+
2044
+ it 'returns both' do
2045
+ expect(plucked_translations_both.first).to eq([{ 'de' => 'deutsch-text', 'en' => 'english-text' }, 'deutsch-text'])
2046
+ end
2047
+ end
2048
+
2049
+ context 'when plucking a specific locale' do
2050
+
2051
+ let(:plucked) do
2052
+ parent.products.all.pluck(:'tagline.de')
2053
+ end
2054
+
2055
+ it 'returns the specific translations' do
2056
+ expect(plucked.first).to eq('deutsch-text')
2057
+ end
2058
+ end
2059
+
2060
+ context 'when plucking a specific locale from _translations field' do
2061
+
2062
+ let(:plucked) do
2063
+ parent.products.all.pluck(:'tagline_translations.de')
2064
+ end
2065
+
2066
+ it 'returns the specific translations' do
2067
+ expect(plucked.first).to eq('deutsch-text')
2068
+ end
2069
+ end
2070
+
2071
+ context 'when fallbacks are enabled with a locale list' do
2072
+ require_fallbacks
2073
+
2074
+ before do
2075
+ I18n.fallbacks[:he] = [:en]
2076
+ end
2077
+
2078
+ let(:plucked) do
2079
+ parent.products.all.pluck(:tagline).first
2080
+ end
2081
+
2082
+ it 'correctly uses the fallback' do
2083
+ I18n.locale = :en
2084
+ parent.products.create!(tagline: 'english-text')
2085
+ I18n.locale = :he
2086
+ expect(plucked).to eq 'english-text'
2087
+ end
2088
+ end
2089
+ end
2090
+
2091
+ context 'when the localized field is embedded' do
2092
+ with_default_i18n_configs
2093
+
2094
+ before do
2095
+ s = Seo.new
2096
+ I18n.locale = :en
2097
+ s.name = 'english-text'
2098
+ I18n.locale = :de
2099
+ s.name = 'deutsch-text'
2100
+
2101
+ parent.products.delete_all
2102
+ parent.products.create!(name: 'ACME Tunnel Paint', seo: s)
2103
+ end
2104
+
2105
+ let(:plucked) do
2106
+ parent.products.pluck('seo.name').first
2107
+ end
2108
+
2109
+ let(:plucked_translations) do
2110
+ parent.products.pluck('seo.name_translations').first
2111
+ end
2112
+
2113
+ let(:plucked_translations_field) do
2114
+ parent.products.pluck('seo.name_translations.en').first
2115
+ end
2116
+
2117
+ it 'returns the translation for the current locale' do
2118
+ expect(plucked).to eq('deutsch-text')
2119
+ end
2120
+
2121
+ it 'returns the full _translation hash' do
2122
+ expect(plucked_translations).to eq({ 'en' => 'english-text', 'de' => 'deutsch-text' })
2123
+ end
2124
+
2125
+ it 'returns the translation for the requested locale' do
2126
+ expect(plucked_translations_field).to eq('english-text')
2127
+ end
2128
+ end
2129
+ end
2130
+
2131
+ context 'when the localized field is embedded and aliased' do
2132
+ with_default_i18n_configs
2133
+
2134
+ before do
2135
+ s = Seo.new
2136
+ I18n.locale = :en
2137
+ s.description = 'english-text'
2138
+ I18n.locale = :de
2139
+ s.description = 'deutsch-text'
2140
+
2141
+ parent.products.delete_all
2142
+ parent.products.create!(name: 'ACME Tunnel Paint', seo: s)
2143
+ end
2144
+
2145
+ let(:plucked) do
2146
+ parent.products.pluck('seo.description').first
2147
+ end
2148
+
2149
+ let(:plucked_unaliased) do
2150
+ parent.products.pluck('seo.desc').first
2151
+ end
2152
+
2153
+ let(:plucked_translations) do
2154
+ parent.products.pluck('seo.description_translations').first
2155
+ end
2156
+
2157
+ let(:plucked_translations_field) do
2158
+ parent.products.pluck('seo.description_translations.en').first
2159
+ end
2160
+
2161
+ it 'returns the translation for the current locale' do
2162
+ I18n.with_locale(:en) do
2163
+ expect(plucked).to eq('english-text')
2164
+ end
2165
+ end
2166
+
2167
+ it 'returns the translation for the current locale when unaliased' do
2168
+ I18n.with_locale(:en) do
2169
+ expect(plucked_unaliased).to eq('english-text')
2170
+ end
2171
+ end
2172
+
2173
+ it 'returns the full _translation hash' do
2174
+ expect(plucked_translations).to eq({ 'en' => 'english-text', 'de' => 'deutsch-text' })
2175
+ end
2176
+
2177
+ it 'returns the translation for the requested locale' do
2178
+ expect(plucked_translations_field).to eq('english-text')
2179
+ end
2180
+ end
2181
+
2182
+ context 'when plucking an embedded field' do
2183
+ let(:label) { Label.new(sales: '1E2') }
2184
+ let!(:band) { Band.create!(label: label) }
2185
+
2186
+ let(:plucked) { Band.where(_id: band.id).pluck('label.sales') }
2187
+
2188
+ it 'demongoizes the field' do
2189
+ expect(plucked).to eq([ BigDecimal('1E2') ])
2190
+ end
2191
+ end
2192
+
2193
+ context 'when plucking an embeds_many field' do
2194
+ let(:label) { Label.new(sales: '1E2') }
2195
+ let!(:band) { Band.create!(labels: [label]) }
2196
+
2197
+ let(:plucked) { Band.where(_id: band.id).pluck('labels.sales') }
2198
+
2199
+ it 'demongoizes the field' do
2200
+ expect(plucked.first).to eq([ BigDecimal('1E2') ])
2201
+ end
2202
+ end
2203
+
2204
+ context 'when plucking a nonexistent embedded field' do
2205
+ let(:label) { Label.new(sales: '1E2') }
2206
+ let!(:band) { Band.create!(label: label) }
2207
+
2208
+ let(:plucked) { Band.where(_id: band.id).pluck('label.qwerty') }
2209
+
2210
+ it 'returns nil' do
2211
+ expect(plucked.first).to eq(nil)
2212
+ end
2213
+ end
2214
+ end
2215
+
1822
2216
  describe "#reset" do
1823
2217
 
1824
2218
  let(:person) do
@@ -1729,6 +1729,19 @@ describe Mongoid::Attributes do
1729
1729
  end
1730
1730
  end
1731
1731
  end
1732
+
1733
+ context 'when map_big_decimal_to_decimal128 is enabled' do
1734
+ config_override :map_big_decimal_to_decimal128, true
1735
+
1736
+ context 'when writing an identical number' do
1737
+ let(:band) { Band.create!(name: 'Nirvana', sales: 123456.78).reload }
1738
+
1739
+ it 'does not mark the document as changed' do
1740
+ band.sales = 123456.78
1741
+ expect(band.changed?).to be false
1742
+ end
1743
+ end
1744
+ end
1732
1745
  end
1733
1746
 
1734
1747
  describe "#typed_value_for" do
@@ -2709,7 +2722,23 @@ describe Mongoid::Attributes do
2709
2722
  end
2710
2723
  end
2711
2724
 
2712
- context "when modifiying a set referenced with the [] notation" do
2725
+ context "when accessing an embedded document with the attribute accessor" do
2726
+ let(:band) { Band.create! }
2727
+
2728
+ before do
2729
+ Band.where(id: band.id).update_all({
2730
+ :$push => {records: { _id: BSON::ObjectId.new }}
2731
+ })
2732
+ end
2733
+
2734
+ it "does not throw a conflicting update error" do
2735
+ b1 = Band.find(band.id)
2736
+ b1[:records].is_a?(Array).should be true
2737
+ expect { b1.save! }.not_to raise_error
2738
+ end
2739
+ end
2740
+
2741
+ context "when modifying a set referenced with the [] notation" do
2713
2742
  let(:catalog) { Catalog.create!(set_field: [ 1 ].to_set) }
2714
2743
 
2715
2744
  before do
@@ -30,6 +30,34 @@ describe Mongoid::Clients::Factory do
30
30
  end
31
31
  end
32
32
 
33
+ shared_examples_for 'includes rails wrapping library' do
34
+ context 'when Rails is available' do
35
+ around do |example|
36
+ rails_was_defined = defined?(::Rails)
37
+
38
+ if !rails_was_defined
39
+ module ::Rails
40
+ def self.version
41
+ '6.1.0'
42
+ end
43
+ end
44
+ end
45
+
46
+ example.run
47
+
48
+ if !rails_was_defined
49
+ Object.send(:remove_const, :Rails) if defined?(::Rails)
50
+ end
51
+ end
52
+
53
+ it 'adds Rails as another wrapping library' do
54
+ expect(client.options[:wrapping_libraries]).to include(
55
+ {'name' => 'Rails', 'version' => '6.1.0'},
56
+ )
57
+ end
58
+ end
59
+ end
60
+
33
61
  describe ".create" do
34
62
 
35
63
  context "when provided a name" do
@@ -89,6 +117,8 @@ describe Mongoid::Clients::Factory do
89
117
  Mongoid::Clients::Factory::MONGOID_WRAPPING_LIBRARY)]
90
118
  end
91
119
 
120
+ it_behaves_like 'includes rails wrapping library'
121
+
92
122
  context 'when configuration specifies a wrapping library' do
93
123
 
94
124
  let(:config) do
@@ -110,6 +140,8 @@ describe Mongoid::Clients::Factory do
110
140
  {'name' => 'Foo'},
111
141
  ]
112
142
  end
143
+
144
+ it_behaves_like 'includes rails wrapping library'
113
145
  end
114
146
  end
115
147
 
@@ -3250,5 +3250,201 @@ describe Mongoid::Criteria do
3250
3250
  expect(criteria.selector).to eq({ 'name' => 'Songs Ohia' })
3251
3251
  end
3252
3252
  end
3253
+
3254
+ context 'with allowed methods' do
3255
+ context 'when using multiple query methods' do
3256
+ let(:hash) do
3257
+ {
3258
+ klass: Band,
3259
+ where: { active: true },
3260
+ limit: 10,
3261
+ skip: 5,
3262
+ order_by: { name: 1 }
3263
+ }
3264
+ end
3265
+
3266
+ it 'applies all methods successfully' do
3267
+ expect(criteria.selector).to eq({ 'active' => true })
3268
+ expect(criteria.options[:limit]).to eq(10)
3269
+ expect(criteria.options[:skip]).to eq(5)
3270
+ expect(criteria.options[:sort]).to eq({ 'name' => 1 })
3271
+ end
3272
+ end
3273
+
3274
+ context 'when using query selector methods' do
3275
+ let(:hash) do
3276
+ {
3277
+ klass: Band,
3278
+ gt: { members: 2 },
3279
+ in: { genre: ['rock', 'metal'] }
3280
+ }
3281
+ end
3282
+
3283
+ it 'applies selector methods' do
3284
+ expect(criteria.selector['members']).to eq({ '$gt' => 2 })
3285
+ expect(criteria.selector['genre']).to eq({ '$in' => ['rock', 'metal'] })
3286
+ end
3287
+ end
3288
+
3289
+ context 'when using aggregation methods' do
3290
+ let(:hash) do
3291
+ {
3292
+ klass: Band,
3293
+ project: { name: 1, members: 1 }
3294
+ }
3295
+ end
3296
+
3297
+ it 'applies aggregation methods' do
3298
+ expect { criteria }.not_to raise_error
3299
+ end
3300
+ end
3301
+ end
3302
+
3303
+ context 'with disallowed methods' do
3304
+ context 'when attempting to call create' do
3305
+ let(:hash) do
3306
+ { klass: Band, create: { name: 'Malicious' } }
3307
+ end
3308
+
3309
+ it 'raises ArgumentError' do
3310
+ expect { criteria }.to raise_error(ArgumentError, "Method 'create' is not allowed in from_hash")
3311
+ end
3312
+ end
3313
+
3314
+ context 'when attempting to call create!' do
3315
+ let(:hash) do
3316
+ { klass: Band, 'create!': { name: 'Malicious' } }
3317
+ end
3318
+
3319
+ it 'raises ArgumentError' do
3320
+ expect { criteria }.to raise_error(ArgumentError, "Method 'create!' is not allowed in from_hash")
3321
+ end
3322
+ end
3323
+
3324
+ context 'when attempting to call build' do
3325
+ let(:hash) do
3326
+ { klass: Band, build: { name: 'Malicious' } }
3327
+ end
3328
+
3329
+ it 'raises ArgumentError' do
3330
+ expect { criteria }.to raise_error(ArgumentError, "Method 'build' is not allowed in from_hash")
3331
+ end
3332
+ end
3333
+
3334
+ context 'when attempting to call find' do
3335
+ let(:hash) do
3336
+ { klass: Band, find: 'some_id' }
3337
+ end
3338
+
3339
+ it 'raises ArgumentError' do
3340
+ expect { criteria }.to raise_error(ArgumentError, "Method 'find' is not allowed in from_hash")
3341
+ end
3342
+ end
3343
+
3344
+ context 'when attempting to call execute_or_raise' do
3345
+ let(:hash) do
3346
+ { klass: Band, execute_or_raise: ['id1', 'id2'] }
3347
+ end
3348
+
3349
+ it 'raises ArgumentError' do
3350
+ expect { criteria }.to raise_error(ArgumentError, "Method 'execute_or_raise' is not allowed in from_hash")
3351
+ end
3352
+ end
3353
+
3354
+ context 'when attempting to call new' do
3355
+ let(:hash) do
3356
+ { klass: Band, new: { name: 'Test' } }
3357
+ end
3358
+
3359
+ it 'raises ArgumentError' do
3360
+ expect { criteria }.to raise_error(ArgumentError, "Method 'new' is not allowed in from_hash")
3361
+ end
3362
+ end
3363
+
3364
+ context 'when allowed method is combined with disallowed method' do
3365
+ let(:hash) do
3366
+ {
3367
+ klass: Band,
3368
+ where: { active: true },
3369
+ create: { name: 'Malicious' }
3370
+ }
3371
+ end
3372
+
3373
+ it 'raises ArgumentError before executing any methods' do
3374
+ expect { criteria }.to raise_error(ArgumentError, "Method 'create' is not allowed in from_hash")
3375
+ end
3376
+ end
3377
+ end
3378
+
3379
+ context 'security validation' do
3380
+ # This test ensures that ALL public methods not in the allowlist are blocked
3381
+ it 'blocks all dangerous public methods' do
3382
+ dangerous_methods = %i[
3383
+ build create create! new
3384
+ find find_or_create_by find_or_create_by! find_or_initialize_by
3385
+ first_or_create first_or_create! first_or_initialize
3386
+ execute_or_raise multiple_from_db for_ids
3387
+ documents= inclusions= scoping_options=
3388
+ initialize freeze as_json
3389
+ ]
3390
+
3391
+ dangerous_methods.each do |method|
3392
+ hash = { klass: Band, method => 'arg' }
3393
+ expect { described_class.from_hash(hash) }.to raise_error(
3394
+ ArgumentError,
3395
+ "Method '#{method}' is not allowed in from_hash"
3396
+ ), "Expected method '#{method}' to be blocked but it was allowed"
3397
+ end
3398
+ end
3399
+
3400
+ it 'blocks dangerous inherited methods from Object' do
3401
+ # Critical security test: block send, instance_eval, etc.
3402
+ inherited_dangerous = %i[
3403
+ send __send__ instance_eval instance_exec
3404
+ instance_variable_set method
3405
+ ]
3406
+
3407
+ inherited_dangerous.each do |method|
3408
+ hash = { klass: Band, method => 'arg' }
3409
+ expect { described_class.from_hash(hash) }.to raise_error(
3410
+ ArgumentError,
3411
+ "Method '#{method}' is not allowed in from_hash"
3412
+ ), "Expected inherited method '#{method}' to be blocked"
3413
+ end
3414
+ end
3415
+
3416
+ it 'blocks Enumerable execution methods' do
3417
+ # from_hash should build queries, not execute them
3418
+ enumerable_methods = %i[each map select count sum]
3419
+
3420
+ enumerable_methods.each do |method|
3421
+ hash = { klass: Band, method => 'arg' }
3422
+ expect { described_class.from_hash(hash) }.to raise_error(
3423
+ ArgumentError,
3424
+ "Method '#{method}' is not allowed in from_hash"
3425
+ ), "Expected Enumerable method '#{method}' to be blocked"
3426
+ end
3427
+ end
3428
+
3429
+ it 'allows all whitelisted methods' do
3430
+ # Sample of allowed methods from each category
3431
+ allowed_sample = {
3432
+ where: { name: 'Test' }, # Query selector
3433
+ limit: 10, # Query option
3434
+ skip: 5, # Query option
3435
+ gt: { age: 18 }, # Query selector
3436
+ in: { status: ['active'] }, # Query selector
3437
+ ascending: :name, # Sorting
3438
+ includes: :notes, # Eager loading
3439
+ merge: { klass: Band }, # Merge
3440
+ }
3441
+
3442
+ allowed_sample.each do |method, args|
3443
+ hash = { klass: Band, method => args }
3444
+ expect { described_class.from_hash(hash) }.not_to raise_error,
3445
+ "Expected method '#{method}' to be allowed but it was blocked"
3446
+ end
3447
+ end
3448
+ end
3253
3449
  end
3254
3450
  end