simple_enum 1.6.9 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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