simple_enum 1.6.9 → 2.0.0.rc1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/LICENSE +1 -1
  4. data/README.md +248 -0
  5. data/Rakefile +4 -21
  6. data/lib/simple_enum/accessors/accessor.rb +55 -0
  7. data/lib/simple_enum/accessors/ignore_accessor.rb +11 -0
  8. data/lib/simple_enum/accessors/whiny_accessor.rb +12 -0
  9. data/lib/simple_enum/accessors.rb +18 -0
  10. data/lib/simple_enum/attribute.rb +77 -0
  11. data/lib/simple_enum/enum.rb +37 -0
  12. data/lib/simple_enum/hasher.rb +26 -0
  13. data/lib/simple_enum/mongoid.rb +11 -15
  14. data/lib/simple_enum/translation.rb +17 -0
  15. data/lib/simple_enum/version.rb +2 -2
  16. data/lib/simple_enum.rb +19 -276
  17. data/simple_enum.gemspec +9 -9
  18. data/spec/simple_enum/accessors_spec.rb +212 -0
  19. data/spec/simple_enum/attribute_spec.rb +208 -0
  20. data/spec/simple_enum/enum_spec.rb +96 -0
  21. data/spec/simple_enum/hasher_spec.rb +63 -0
  22. data/spec/simple_enum/mongoid_spec.rb +44 -0
  23. data/spec/simple_enum/translation_spec.rb +66 -0
  24. data/spec/spec_helper.rb +27 -0
  25. data/spec/support/active_record_support.rb +23 -0
  26. data/spec/support/i18n_support.rb +12 -0
  27. data/spec/support/model_support.rb +47 -0
  28. data/spec/support/mongoid_support.rb +21 -0
  29. metadata +50 -56
  30. data/README.rdoc +0 -293
  31. data/lib/simple_enum/enum_hash.rb +0 -64
  32. data/lib/simple_enum/validation.rb +0 -58
  33. data/locales/en.yml +0 -10
  34. data/test/array_conversions_test.rb +0 -21
  35. data/test/class_methods_test.rb +0 -114
  36. data/test/dirty_attributes_test.rb +0 -37
  37. data/test/enum_hash_test.rb +0 -73
  38. data/test/finders_test.rb +0 -45
  39. data/test/locales.yml +0 -25
  40. data/test/mongoid_test.rb +0 -66
  41. data/test/object_backed_test.rb +0 -61
  42. data/test/orm/active_record.rb +0 -114
  43. data/test/orm/common.rb +0 -23
  44. data/test/orm/mongoid.rb +0 -114
  45. data/test/poro_test.rb +0 -20
  46. data/test/prefixes_test.rb +0 -36
  47. data/test/simple_enum_test.rb +0 -314
  48. data/test/test_helper.rb +0 -40
  49. data/test/without_shortcuts_test.rb +0 -39
data/lib/simple_enum.rb CHANGED
@@ -3,299 +3,42 @@
3
3
  # but instead on integer columns.
4
4
  #
5
5
  # Author:: Lukas Westermann
6
- # Copyright:: Copyright (c) 2009 Lukas Westermann (Zurich, Switzerland)
7
- # Licence:: MIT-Licence (http://www.opensource.org/licenses/mit-license.php)
6
+ # Copyright:: Copyright (c) 2009-2014 Lukas Westermann (Zurich, Switzerland)
7
+ # License:: MIT-Licence (http://www.opensource.org/licenses/mit-license.php)
8
8
  #
9
9
  # See the +as_enum+ documentation for more details.
10
10
 
11
- # because we depend on i18n and activesupport
12
- require 'i18n'
13
11
  require 'active_support'
14
12
 
15
- require 'simple_enum/enum_hash'
16
- require 'simple_enum/validation'
17
-
18
- require 'active_support/deprecation'
13
+ require 'simple_enum/version'
14
+ require 'simple_enum/attribute'
15
+ require 'simple_enum/translation'
19
16
 
20
17
  # Base module which gets included in <tt>ActiveRecord::Base</tt>. See documentation
21
18
  # of +SimpleEnum::ClassMethods+ for more details.
22
19
  module SimpleEnum
20
+ mattr_accessor :with
21
+ @@with = [:attribute, :dirty, :scope]
23
22
 
24
- class << self
25
-
26
- # Provides configurability to SimpleEnum, allows to override some defaults which are
27
- # defined for all uses of +as_enum+. Most options from +as_enum+ are available, such as:
28
- # * <tt>:prefix</tt> - Define a prefix, which is prefixed to the shortcut methods (e.g. <tt><symbol>!</tt> and
29
- # <tt><symbol>?</tt>), if it's set to <tt>true</tt> the enumeration name is used as a prefix, else a custom
30
- # prefix (symbol or string) (default is <tt>nil</tt> => no prefix)
31
- # * <tt>:slim</tt> - If set to <tt>true</tt> no shortcut methods for all enumeration values are being generated, if
32
- # set to <tt>:class</tt> only class-level shortcut methods are disabled (default is <tt>nil</tt> => they are generated)
33
- # * <tt>:upcase</tt> - If set to +true+ the <tt>Klass.foos</tt> is named <tt>Klass.FOOS</tt>, why? To better suite some
34
- # coding-styles (default is +false+ => downcase)
35
- # * <tt>:whiny</tt> - Boolean value which if set to <tt>true</tt> will throw an <tt>ArgumentError</tt>
36
- # if an invalid value is passed to the setter (e.g. a value for which no enumeration exists). if set to
37
- # <tt>false</tt> no exception is thrown and the internal value is set to <tt>nil</tt> (default is <tt>true</tt>)
38
- # * <tt>:dirty</tt> - Boolean value which if set to <tt>true</tt> generates <tt>..._was</tt> and <tt>..._changed?</tt>
39
- # methods for the enum, which delegate to the internal column.
40
- # * <tt>:strings</tt> - Boolean value which if set to <tt>true</tt> defaults array values as strings instead of integers.
41
- def default_options
42
- @default_options ||= {
43
- :whiny => true,
44
- :upcase => false
45
- }
46
- end
47
-
48
- def included(base) #:nodoc:
49
- base.send :class_attribute, :simple_enum_definitions, :instance_writer => false, :instance_reader => false
50
- base.send :extend, ClassMethods
51
- end
52
- end
53
-
54
- module ClassMethods
55
-
56
- # Provides ability to create simple enumerations based on hashes or arrays, backed
57
- # by integer columns (but not limited to integer columns).
58
- #
59
- # Columns are supposed to be suffixed by <tt>_cd</tt>, if not, use <tt>:column => 'the_column_name'</tt>,
60
- # so some example migrations:
61
- #
62
- # add_column :users, :gender_cd, :integer
63
- # add_column :users, :status, :integer # and a custom column...
64
- #
65
- # and then in your model:
66
- #
67
- # class User < ActiveRecord::Base
68
- # as_enum :gender, [:male, :female]
69
- # end
70
- #
71
- # # or use a hash:
72
- #
73
- # class User < ActiveRecord::Base
74
- # as_enum :user_status, { :active => 1, :inactive => 0, :archived => 2, :deleted => 3 }, :column => 'status'
75
- # end
76
- #
77
- # Now it's possible to access the enumeration and the internally stored value like:
78
- #
79
- # john_doe = User.new
80
- # john_doe.gender # => nil
81
- # john_doe.gender = :male
82
- # john_doe.gender # => :male
83
- # john_doe.gender_cd # => 0
84
- #
85
- # And to make life a tad easier: a few shortcut methods to work with the enumeration are also created.
86
- #
87
- # john_doe.male? # => true
88
- # john_doe.female? # => false
89
- # john_doe.female! # => :female (set's gender to :female => gender_cd = 1)
90
- # john_doe.male? # => false
91
- #
92
- # Sometimes it's required to access the db-backed values, like e.g. in a query:
93
- #
94
- # User.genders # => { :male => 0, :female => 1}, values hash
95
- # User.genders(:male) # => 0, value access (via hash)
96
- # User.female # => 1, direct access
97
- # User.find :all, :conditions => { :gender_cd => User.female } # => [...], list with all women
98
- #
99
- # To access the key/value assocations in a helper like the select helper or similar use:
100
- #
101
- # <%= select(:user, :gender, User.genders.keys)
102
- #
103
- # The generated shortcut methods (like <tt>male?</tt> or <tt>female!</tt> etc.) can also be prefixed
104
- # using the <tt>:prefix</tt> option. If the value is <tt>true</tt>, the shortcut methods are prefixed
105
- # with the name of the enumeration.
106
- #
107
- # class User < ActiveRecord::Base
108
- # as_enum :gender, [:male, :female], :prefix => true
109
- # end
110
- #
111
- # jane_doe = User.new
112
- # jane_doe.gender = :female # this is still as-is
113
- # jane_doe.gender_cd # => 1, and so it this
114
- #
115
- # jane_doe.gender_female? # => true (instead of jane_doe.female?)
116
- #
117
- # It is also possible to supply a custom prefix.
118
- #
119
- # class Item < ActiveRecord::Base
120
- # as_enum :status, [:inactive, :active, :deleted], :prefix => :state
121
- # end
122
- #
123
- # item = Item.new(:status => :active)
124
- # item.state_inactive? # => false
125
- # Item.state_deleted # => 2
126
- # Item.status(:deleted) # => 2, same as above...
127
- #
128
- # To disable the generation of the shortcut methods for all enumeration values, add <tt>:slim => true</tt> to
129
- # the options.
130
- #
131
- # class Address < ActiveRecord::Base
132
- # as_enum :canton, {:aargau => 'ag', ..., :wallis => 'vs', :zug => 'zg', :zurich => 'zh'}, :slim => true
133
- # end
134
- #
135
- # home = Address.new(:canton => :zurich, :street => 'Bahnhofstrasse 1', ...)
136
- # home.canton # => :zurich
137
- # home.canton_cd # => 'zh'
138
- # home.aargau! # throws NoMethodError: undefined method `aargau!'
139
- # Address.aargau # throws NoMethodError: undefined method `aargau`
140
- #
141
- # This is especially useful if there are (too) many enumeration values, or these shortcut methods
142
- # are not required.
143
- #
144
- # === Configuration options:
145
- # * <tt>:column</tt> - Specifies a custom column name, instead of the default suffixed <tt>_cd</tt> column
146
- # * <tt>:prefix</tt> - Define a prefix, which is prefixed to the shortcut methods (e.g. <tt><symbol>!</tt> and
147
- # <tt><symbol>?</tt>), if it's set to <tt>true</tt> the enumeration name is used as a prefix, else a custom
148
- # prefix (symbol or string) (default is <tt>nil</tt> => no prefix)
149
- # * <tt>:slim</tt> - If set to <tt>true</tt> no shortcut methods for all enumeration values are being generated, if
150
- # set to <tt>:class</tt> only class-level shortcut methods are disabled (default is <tt>nil</tt> => they are generated)
151
- # * <tt>:upcase</tt> - If set to +true+ the <tt>Klass.foos</tt> is named <tt>Klass.FOOS</tt>, why? To better suite some
152
- # coding-styles (default is +false+ => downcase)
153
- # * <tt>:whiny</tt> - Boolean value which if set to <tt>true</tt> will throw an <tt>ArgumentError</tt>
154
- # if an invalid value is passed to the setter (e.g. a value for which no enumeration exists). if set to
155
- # <tt>false</tt> no exception is thrown and the internal value is set to <tt>nil</tt> (default is <tt>true</tt>)
156
- # * <tt>:dirty</tt> - Boolean value which if set to <tt>true</tt> generates <tt>..._was</tt> and <tt>..._changed?</tt>
157
- # methods for the enum, which delegate to the internal column (default is <tt>false</tt>)
158
- # * <tt>:strings</tt> - Boolean value which if set to <tt>true</tt> stores array values as strings instead of it's index.
159
- # * <tt>:field</tt> - Also allowed as valid key, for Mongoid integration + default options, see simple_enum#27.
160
- #
161
- def as_enum(enum_cd, values, options = {})
162
- options = SimpleEnum.default_options.merge({ :column => "#{enum_cd}_cd" }).merge(options)
163
- options.assert_valid_keys(:column, :whiny, :prefix, :slim, :upcase, :dirty, :strings, :field)
164
-
165
- metaclass = (class << self; self; end)
166
-
167
- # convert array to hash
168
- values = SimpleEnum::EnumHash.new(values, options[:strings])
169
- values_inverted = values.invert
170
-
171
- # store info away
172
- self.enum_definitions[enum_cd] = self.enum_definitions[options[:column]] = { :name => enum_cd, :column => options[:column], :options => options }
173
-
174
- # raise error if enum_cd == column
175
- raise ArgumentError, "[simple_enum] use different names for #{enum_cd}'s name and column name." if enum_cd.to_s == options[:column].to_s
176
-
177
- # generate getter
178
- define_method("#{enum_cd}") do
179
- id = send(options[:column])
180
- values_inverted[id]
181
- end
182
-
183
- # generate setter
184
- define_method("#{enum_cd}=") do |new_value|
185
- return send("#{options[:column]}=", nil) if new_value.blank?
186
-
187
- new_value = new_value.to_s if options[:strings]
188
- real = nil
189
- if values.contains?(new_value)
190
- real = values[EnumHash.symbolize(new_value)]
191
- real = new_value if real.nil? && values_inverted[new_value].present?
192
- end
193
-
194
- raise ArgumentError, "Invalid enumeration value: #{new_value}" if options[:whiny] && !real
195
- send("#{options[:column]}=", real)
196
- end
197
-
198
- # generate checker
199
- define_method("#{enum_cd}?") do |*args|
200
- current = send(enum_cd)
201
- return current.to_s == args.first.to_s if args.length > 0
23
+ mattr_accessor :accessor
24
+ @@accessor = :default
202
25
 
203
- !!current
204
- end
26
+ mattr_accessor :builder
27
+ @@builder = :default
205
28
 
206
- # support dirty attributes by delegating to column, currently opt-in
207
- if options[:dirty]
208
- define_method("#{enum_cd}_changed?") do
209
- self.send("#{options[:column]}_changed?")
210
- end
29
+ mattr_accessor :suffix
30
+ @@suffix = "_cd"
211
31
 
212
- define_method("#{enum_cd}_was") do
213
- values_inverted[self.send("#{options[:column]}_was")]
214
- end
215
- end
32
+ mattr_accessor :field
33
+ @@field = {}
216
34
 
217
- # allow access to defined values hash, e.g. in a select helper or finder method.
218
- attr_name = enum_cd.to_s.pluralize
219
- enum_attr = :"#{attr_name.downcase}_enum_hash"
220
-
221
- define_method("human_#{enum_cd}") do
222
- self.class.human_enum_name(attr_name, self.send(enum_cd)) unless self.send(enum_cd).nil?
223
- end
224
-
225
- # generate find_by enum singleton
226
- (class << self; self end).send(:define_method, "find_by_#{enum_cd}") do |*args|
227
- send("find_by_#{options[:column]}", args)
228
- end
229
-
230
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
231
- class_attribute #{enum_attr.inspect}, :instance_writer => false, :instance_reader => false
232
-
233
- def self.#{attr_name}(*args)
234
- return #{enum_attr} if args.first.nil?
235
- return #{enum_attr}[args.first] if args.size == 1
236
- args.inject([]) { |ary, sym| ary << #{enum_attr}[sym]; ary }
237
- end
238
-
239
- def self.#{attr_name}_for_select(attr = :key, &block)
240
- self.#{attr_name}.map do |k,v|
241
- [block_given? ? yield(k,v) : self.human_enum_name(#{attr_name.inspect}, k), attr == :value ? v : k]
242
- end
243
- end
244
- RUBY
245
-
246
- # write values
247
- self.send "#{enum_attr}=", values
248
-
249
- # only create if :slim is not defined
250
- if options[:slim] != true
251
- # create both, boolean operations and *bang* operations for each
252
- # enum "value"
253
- prefix = options[:prefix] && "#{options[:prefix] == true ? enum_cd : options[:prefix]}_"
254
-
255
- values.each do |k,code|
256
- sym = EnumHash.symbolize(k)
257
-
258
- define_method("#{prefix}#{sym}?") do
259
- current = send(options[:column])
260
- code == current
261
- end
262
- define_method("#{prefix}#{sym}!") do
263
- send("#{options[:column]}=", code)
264
- sym
265
- end
266
-
267
- # allow class access to each value
268
- unless options[:slim] === :class
269
- metaclass.send(:define_method, "#{prefix}#{sym}", Proc.new { |*args| args.first ? k : code })
270
- end
271
- end
272
- end
273
- end
274
-
275
- def human_enum_name(enum, key, options = {})
276
- defaults = lookup_ancestors.map do |klass|
277
- :"#{self.i18n_scope}.enums.#{klass.model_name.i18n_key}.#{enum}.#{key}"
278
- end
279
-
280
- defaults << :"enums.#{self.model_name.i18n_key}.#{enum}.#{key}"
281
- defaults << :"enums.#{enum}.#{key}"
282
- defaults << options.delete(:default) if options[:default]
283
- defaults << key.to_s.humanize
284
-
285
- options.reverse_merge! :count => 1, :default => defaults
286
- I18n.translate(defaults.shift, options)
287
- end
288
-
289
- def enum_definitions
290
- self.simple_enum_definitions ||= {}
291
- end
35
+ def self.configure
36
+ yield(self)
292
37
  end
293
38
  end
294
39
 
295
40
  # include in AR
296
41
  ActiveSupport.on_load(:active_record) do
297
- ActiveRecord::Base.send(:include, SimpleEnum)
42
+ ActiveRecord::Base.send(:extend, SimpleEnum::Attribute)
43
+ ActiveRecord::Base.send(:extend, SimpleEnum::Translation)
298
44
  end
299
-
300
- # setup i18n load path...
301
- I18n.load_path << File.join(File.dirname(__FILE__), '..', 'locales', 'en.yml')
data/simple_enum.gemspec CHANGED
@@ -8,23 +8,23 @@ Gem::Specification.new do |s|
8
8
  s.summary = "Simple enum-like field support for models."
9
9
  s.description = "Provides enum-like fields for ActiveRecord, ActiveModel and Mongoid models."
10
10
 
11
- s.required_ruby_version = ">= 1.8.7"
12
- s.required_rubygems_version = ">= 1.3.6"
11
+ s.required_ruby_version = ">= 1.9.3"
12
+ s.required_rubygems_version = ">= 2.0.0"
13
13
 
14
14
  s.authors = ["Lukas Westermann"]
15
15
  s.email = ["lukas.westermann@gmail.com"]
16
16
  s.homepage = "http://lwe.github.com/simple_enum/"
17
17
 
18
- s.files = %w{.gitignore Rakefile Gemfile README.rdoc LICENSE simple_enum.gemspec} + Dir['**/*.{rb,yml}']
19
- s.test_files = s.files.grep(%r{^(test|spec|locales)/})
18
+ s.files = %w{.gitignore Rakefile Gemfile README.md LICENSE simple_enum.gemspec} + Dir['**/*.{rb,yml}']
19
+ s.test_files = s.files.grep(%r{^(test|spec)/})
20
20
  s.require_paths = %w{lib}
21
21
 
22
22
  s.license = 'MIT'
23
23
 
24
- s.add_dependency "activesupport", '>= 3.0.0'
24
+ s.add_dependency 'activesupport', '>= 4.0.0'
25
25
 
26
- s.add_development_dependency 'rake', '>= 0.9.2'
27
- s.add_development_dependency 'minitest', '~> 2.0'
28
- s.add_development_dependency 'activerecord', '>= 3.0.0'
29
- s.add_development_dependency 'mongoid', '~> 2.0'
26
+ s.add_development_dependency 'rake', '>= 10.1.0'
27
+ s.add_development_dependency 'activerecord', '>= 4.0.0'
28
+ s.add_development_dependency 'mongoid', '>= 4.0.0.beta1'
29
+ s.add_development_dependency 'rspec', '~> 2.14'
30
30
  end
@@ -0,0 +1,212 @@
1
+ require 'spec_helper'
2
+
3
+ describe SimpleEnum::Accessors do
4
+ let(:enum) { SimpleEnum::Enum.new(:gender, "male" => 0, "female" => 1) }
5
+ fake_model(:klass)
6
+ let(:object) { klass.new }
7
+
8
+ context '.accessor' do
9
+ it 'returns Accessor instance' do
10
+ expect(described_class.accessor(:gender, enum)).to be_a(described_class::Accessor)
11
+ end
12
+
13
+ it 'returns a WhinyAccessor instance if accessor: :whiny' do
14
+ expect(described_class.accessor(:gender, enum, accessor: :whiny)).to be_a(described_class::WhinyAccessor)
15
+ end
16
+
17
+ it 'sets source to "gender" if source: :gender' do
18
+ expect(described_class.accessor(:gender, enum, source: :gender).source).to eq 'gender'
19
+ end
20
+ end
21
+
22
+ context 'Accessor' do
23
+ subject { described_class::Accessor.new(:gender, enum) }
24
+
25
+ context '#name' do
26
+ it 'returns the enum name as string' do
27
+ expect(subject.name).to eq 'gender'
28
+ end
29
+ end
30
+
31
+ context '#to_s' do
32
+ it 'returns the name' do
33
+ expect(subject.to_s).to eq 'gender'
34
+ end
35
+ end
36
+
37
+ context '#prefix' do
38
+ it 'returns empty string when prefix is nil' do
39
+ expect(described_class::Accessor.new(:gender, enum).prefix).to eq ''
40
+ end
41
+
42
+ it 'returns gender_ when prefix is true' do
43
+ expect(described_class::Accessor.new(:gender, enum, nil, true).prefix).to eq 'gender_'
44
+ end
45
+
46
+ it 'returns other_ when prefix is "other"' do
47
+ expect(described_class::Accessor.new(:gender, hash, nil, 'other').prefix).to eq 'other_'
48
+ end
49
+ end
50
+
51
+ context '#source' do
52
+ it 'returns gender_cd when source is nil' do
53
+ expect(described_class::Accessor.new(:gender, hash, nil).source).to eq 'gender_cd'
54
+ end
55
+
56
+ it 'returns "some_column" when source is set to :some_column' do
57
+ expect(described_class::Accessor.new(:gender, hash, :some_column).source).to eq 'some_column'
58
+ end
59
+
60
+ it 'returns "gender" when source is set to "gender"' do
61
+ expect(described_class::Accessor.new(:gender, hash, 'gender').source).to eq 'gender'
62
+ end
63
+ end
64
+
65
+ context '#read' do
66
+ shared_examples_for 'reading an enum' do
67
+ it 'returns nil then gender_cd is nil' do
68
+ expect(subject.read(object)).to be_nil
69
+ end
70
+
71
+ it 'returns :male when gender_cd is 0' do
72
+ expect(subject.read(klass.new(0))).to eq :male
73
+ end
74
+
75
+ it 'returns :female when gender_cd is 1' do
76
+ expect(subject.read(klass.new(1))).to eq :female
77
+ end
78
+ end
79
+
80
+ it_behaves_like 'reading an enum'
81
+
82
+ context 'with name == source' do
83
+ subject { described_class::Accessor.new(:gender_cd, enum, :gender_cd) }
84
+ it_behaves_like 'reading an enum'
85
+ end
86
+ end
87
+
88
+ context '#write' do
89
+ shared_examples_for 'writing an enum' do
90
+ it 'writes nil to object' do
91
+ object = klass.new(0)
92
+ expect(subject.write(object, nil)).to be_nil
93
+ expect(object.gender_cd).to be_nil
94
+ end
95
+
96
+ it 'writes 1 to object with :female' do
97
+ expect(subject.write(object, :female)).to eq :female
98
+ expect(object.gender_cd).to eq 1
99
+ end
100
+
101
+ it 'writes 0 to object with "male"' do
102
+ expect(subject.write(object, 'male')).to eq 'male'
103
+ expect(object.gender_cd).to eq 0
104
+ end
105
+
106
+ it 'writes 1 to object with 1' do
107
+ expect(subject.write(object, 1)).to eq 1
108
+ expect(object.gender_cd).to eq 1
109
+ end
110
+
111
+ it 'writes nil to object with :other' do
112
+ object = klass.new(1)
113
+ expect(subject.write(object, :other)).to be_nil
114
+ expect(object.gender_cd).to be_nil
115
+ end
116
+ end
117
+
118
+ it_behaves_like 'writing an enum'
119
+
120
+ context 'with name == source' do
121
+ subject { described_class::Accessor.new(:gender_cd, enum, :gender_cd) }
122
+ it_behaves_like 'writing an enum'
123
+ end
124
+ end
125
+
126
+ context '#selected?' do
127
+ it 'returns false when gender_cd is nil' do
128
+ expect(subject.selected?(object)).to be_false
129
+ expect(subject.selected?(object, :male)).to be_false
130
+ end
131
+
132
+ it 'returns true when gender_cd is != nil' do
133
+ expect(subject.selected?(klass.new(0))).to be_true
134
+ expect(subject.selected?(klass.new(1))).to be_true
135
+ end
136
+
137
+ it 'returns true when gender_cd is 0 and :male is passed' do
138
+ expect(subject.selected?(klass.new(0), :male)).to be_true
139
+ end
140
+
141
+ it 'returns false when gender_cd is 0 and :female is passed' do
142
+ expect(subject.selected?(klass.new(0), :female)).to be_false
143
+ end
144
+
145
+ it 'returns false when gender_cd is 1 and :other is passed' do
146
+ expect(subject.selected?(klass.new(0), :other)).to be_false
147
+ end
148
+ end
149
+
150
+ context '#changed?' do
151
+ it 'delegates to attribute_changed?' do
152
+ expect(object).to receive(:attribute_changed?).with('gender_cd') { true }
153
+ expect(subject.changed?(object)).to be_true
154
+ end
155
+ end
156
+
157
+ context 'was' do
158
+ it 'delegates to attribute_was and resolves symbol' do
159
+ expect(object).to receive(:attribute_was).with('gender_cd') { 1 }
160
+ expect(subject.was(object)).to eq :female
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'IgnoreAccessor' do
166
+ subject { described_class::IgnoreAccessor.new(:gender, enum) }
167
+
168
+ it 'sets gender_cd to 0 with symbol' do
169
+ expect(subject.write(object, :male)).to_not be_false
170
+ expect(object.gender_cd).to eq 0
171
+ end
172
+
173
+ it 'sets gender_cd to 1 via value (1)' do
174
+ expect(subject.write(object, 1)).to_not be_false
175
+ expect(object.gender_cd).to eq 1
176
+ end
177
+
178
+ it 'sets gender_cd to nil' do
179
+ expect(subject.write(object, nil)).to be_false
180
+ expect(object.gender_cd).to be_nil
181
+ end
182
+
183
+ it 'keeps existing value when unknown value is passed' do
184
+ object.gender_cd = 1
185
+ expect(subject.write(object, :other)).to be_false
186
+ expect(object.gender_cd).to eq 1
187
+ end
188
+ end
189
+
190
+ context 'WhinyAccessor' do
191
+ subject { described_class::WhinyAccessor.new(:gender, enum) }
192
+
193
+ it 'raises no error when setting existing key' do
194
+ expect { subject.write(object, :male) }.to_not raise_error
195
+ expect(object.gender_cd).to eq 0
196
+ end
197
+
198
+ it 'raises no error when setting with existing value' do
199
+ expect { subject.write(object, 1) }.to_not raise_error
200
+ expect(object.gender_cd).to eq 1
201
+ end
202
+
203
+ it 'raises no error when setting to nil' do
204
+ expect { subject.write(object, nil) }.to_not raise_error
205
+ expect(object.gender_cd).to be_nil
206
+ end
207
+
208
+ it 'raises ArgumentError when setting invalid key' do
209
+ expect { subject.write(object, :other) }.to raise_error(ArgumentError)
210
+ end
211
+ end
212
+ end