mongoid_enum 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b67dcfd5fb5d061dd20797d9ebe7dc54114030d2
4
+ data.tar.gz: 982b2d073dd01bb563f8446b05f37a9e30dee7a7
5
+ SHA512:
6
+ metadata.gz: 19171e4d95f8c56c387306f2745af4a685ac3122e5d9804dfe9bc1db84866a32fdde3fa90f5d2805abe8a6522f9d44d2d63122c94739412a24378bccbd92f13d
7
+ data.tar.gz: 0732be4b09f52e3b8a6b3896d52c61b51aa5fbdcf31a3d9212ca7b26e7bc92e8f06733c22b30a251025119b5150309981a638d5ea23c91122c025cb16cd55ebe
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2013 David Heinemeier Hansson and other Rails contributors
2
+ Copyright (c) 2015 Adam Wróbel
3
+
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Introduction
2
+
3
+ Enum implementation for Mongoid. Similar to that found in ActiveRecord.
4
+
5
+ # Installation
6
+
7
+ gem "mongoid_enum"
8
+
9
+ # Usage
10
+
11
+ By default values saved to DB are strings matching enum labels.
12
+
13
+ class Conversation
14
+ include Mongoid::Document
15
+ include Mongoid::Enum
16
+
17
+ enum status: [ :active, :archived ]
18
+ end
19
+
20
+ conversation = Conversation.active.first
21
+ conversation.active? # true
22
+ conversation.archived! # immediately saves document
23
+ conversation.active? # false
24
+ conversation["status"] # "archived"
25
+
26
+ You can use other Mongo value types (numbers, booleans, nil) when explicitly defining
27
+ the enum mapping:
28
+
29
+ class Part
30
+ include Mongoid::Document
31
+ include Mongoid::Enum
32
+
33
+ enum quality_control: {pending: nil, passed: true, failed: false}, _prefix: :qc
34
+ end
35
+
36
+ part = Part.qc_pending.first
37
+ part.quality_control # "pending"
38
+ part.qc_passed!
39
+ part.quality_control # "passed"
40
+ part["quality_control"] # true
41
+
42
+ Enum values are validated.
43
+
44
+ part.quality_control = "unknown value"
45
+ part.valid? # false
46
+
47
+ You can access the mapping as hash with indifferent access via class level constant.
48
+ Constant name is a pluralized field name set in SCREAMING\_SNAKE\_CASE.
49
+
50
+ Part::QUALITY_CONTROLS["passed"] # true
51
+ Part::QUALITY_CONTROLS[:failed] # false
52
+
53
+ # Differences from ActiveRecord
54
+
55
+ 1. Default values are strings, not integers. I think it fits MongoDB better.
56
+
57
+ 2. Mapping hash is accessible via class constant instead of class method. I prefer
58
+ constant because it is accessible without prefix both on class methods and instance
59
+ methods. Also I think SCREAMING\_SNAKE\_CASE is less likely to cause name conflicts.
60
+
61
+ 3. I do not raise exception when invalid value is assigned. Instead document fails
62
+ validation. Assigned invalid value is remembered and can be corrected in forms.
63
+ Exception will still be raised if you try to save invalid label by skipping validation
64
+ (`save validate: false`). That's because the document does not know how to convert
65
+ invalid enum option to database value.
66
+
67
+ # Credits
68
+
69
+ Original implementation for ActiveRecord was created by David Heinemeier Hansson.
70
+ Following modifications by other Rails contributors. Port to Mongoid prepared by Adam
71
+ Wróbel.
@@ -0,0 +1,43 @@
1
+ module Mongoid
2
+ module Enum
3
+ # Internal: Mapping wrapper used as type for enum fields.
4
+ #
5
+ # Handles label-value conversion.
6
+ class EnumType # :nodoc:
7
+ attr_reader :mappings
8
+
9
+ def initialize(mappings)
10
+ @mappings = mappings
11
+ end
12
+
13
+ def mongoize(key)
14
+ if key.blank?
15
+ value = nil
16
+ elsif mappings.key? key
17
+ value = mappings[key]
18
+ elsif mappings.value? key
19
+ # XXX Mongoid may try to mongoize multiple times so if key is a valid value
20
+ # assume that it has already been converted to value.
21
+ value = key
22
+ else
23
+ value = Enum::InvalidKey.new(key)
24
+ end
25
+ value
26
+ end
27
+ alias_method :evolve, :mongoize
28
+
29
+ def demongoize(value)
30
+ if mappings.value? value
31
+ key = mappings.key value
32
+ elsif value.blank?
33
+ key = nil
34
+ elsif value.is_a? Enum::InvalidKey
35
+ key = value.original_key
36
+ else
37
+ key = Enum::InvalidValue.new(value)
38
+ end
39
+ key
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ module Mongoid
2
+ module Enum
3
+ # raised when InvalidKey is forced to be saved in DB (skipping validation)
4
+ class InvalidKeyError < StandardError
5
+ end
6
+
7
+ # Internal: Wraps invalid keys passed to setter so that getter returns the same key.
8
+ class InvalidKey # :nodoc:
9
+ attr_reader :original_key
10
+
11
+ def initialize(key)
12
+ @original_key = key
13
+ end
14
+
15
+ def raise_error
16
+ raise InvalidKeyError, "invalid enum key: #{original_key}"
17
+ end
18
+ alias_method :bson_type, :raise_error
19
+ alias_method :to_bson, :raise_error
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module Mongoid
2
+ module Enum
3
+ # Public: Wraps invalid values loaded from DB before returning them via getter.
4
+ class InvalidValue
5
+ # Public: Holds the value loaded from DB that is not part of defined enum.
6
+ attr_reader :database_value
7
+
8
+ # :nodoc:
9
+ def initialize(value)
10
+ @database_value = value
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,255 @@
1
+ require "mongoid/enum/enum_type"
2
+ require "mongoid/enum/invalid_key"
3
+ require "mongoid/enum/invalid_value"
4
+
5
+ module Mongoid # :nodoc:
6
+ # Declare an enum field with scope and value checking helper methods. Example:
7
+ #
8
+ # class Conversation
9
+ # include Mongoid::Document
10
+ # include Mongoid::Enum
11
+ #
12
+ # enum status: [ :active, :archived ]
13
+ # end
14
+ #
15
+ # # conversation.update! status: "active"
16
+ # conversation.active!
17
+ # conversation.active? # => true
18
+ # conversation.status # => "active"
19
+ #
20
+ # # conversation.update! status: "archived"
21
+ # conversation.archived!
22
+ # conversation.archived? # => true
23
+ # conversation.status # => "archived"
24
+ #
25
+ # conversation.status = nil
26
+ # conversation.status.nil? # => true
27
+ # conversation.status # => nil
28
+ #
29
+ # Scopes based on the allowed values of the enum field will be provided
30
+ # as well. With the above example:
31
+ #
32
+ # Conversation.active
33
+ # Conversation.archived
34
+ #
35
+ # Of course, you can also query them directly if the scopes don't fit your
36
+ # needs:
37
+ #
38
+ # Conversation.where(status: [:active, :archived])
39
+ # Conversation.not.where(status: :active)
40
+ #
41
+ # You can set the default value:
42
+ #
43
+ # class Conversation
44
+ # include Mongoid::Document
45
+ # include Mongoid::Enum
46
+ #
47
+ # enum status: [ :active, :archived ], _default: :active
48
+ # end
49
+ #
50
+ # Finally, it's also possible to explicitly map the relation between attribute and
51
+ # values stored in the database with a hash. The values don't even need to be Strings.
52
+ # Numbers, Booleans, nil and some others are allowed.
53
+ #
54
+ # class Conversation
55
+ # include Mongoid::Document
56
+ # include Mongoid::Enum
57
+ #
58
+ # enum status: { active: 0, archived: 1 }
59
+ # end
60
+ #
61
+ # The mappings are exposed through a class constant with the pluralized attribute
62
+ # name. It defines the mapping in a +HashWithIndifferentAccess+:
63
+ #
64
+ # Conversation::STATUSES[:active] # => 0
65
+ # Conversation::STATUSES["archived"] # => 1
66
+ #
67
+ # Defining an enum automatically adds a validator on its field. Assigning values
68
+ # not included in enum definition will make the document invalid.
69
+ #
70
+ # conversation = Conversation.new
71
+ # conversation.status = :unknown
72
+ # conversation.valid? # false
73
+ #
74
+ # Default validator allows nil values. Add your own presence validator if you require
75
+ # a value for the enum field.
76
+ #
77
+ # class Conversation
78
+ # include Mongoid::Document
79
+ # include Mongoid::Enum
80
+ #
81
+ # enum status: { active: 0, archived: 1 }
82
+ #
83
+ # validates :status, presence: true
84
+ # end
85
+ #
86
+ #
87
+ # You can use the +:_prefix+ or +:_suffix+ options when you need to define
88
+ # multiple enums with same values. If the passed value is +true+, the methods
89
+ # are prefixed/suffixed with the name of the field. It is also possible to
90
+ # supply a custom value:
91
+ #
92
+ # class Conversation
93
+ # include Mongoid::Document
94
+ # include Mongoid::Enum
95
+ #
96
+ # enum status: [:active, :archived], _suffix: true
97
+ # enum comments_status: [:active, :inactive], _prefix: :comments
98
+ # end
99
+ #
100
+ # With the above example, the bang and predicate methods along with the
101
+ # associated scopes are now prefixed and/or suffixed accordingly:
102
+ #
103
+ # conversation.active_status!
104
+ # conversation.archived_status? # => false
105
+ #
106
+ # conversation.comments_inactive!
107
+ # conversation.comments_active? # => false
108
+ #
109
+ # If you want to you can give nil value an explicit label.
110
+ #
111
+ # class Part
112
+ # include Mongoid::Document
113
+ # include Mongoid::Enum
114
+ #
115
+ # enum quality_control: {pending: nil, passed: true, failed: false}, _prefix: :qc
116
+ # end
117
+ #
118
+ # part = Part.qc_pending.first
119
+ # part.qc_pending? # true
120
+ # part.qc_passed!
121
+ # part.qc_pending? # false
122
+ module Enum
123
+ extend ActiveSupport::Concern
124
+
125
+ included do
126
+ class_attribute(:enums)
127
+ self.enums = {}
128
+ end
129
+
130
+ # :nodoc:
131
+ module ClassMethods
132
+ # Define enum field on the model. See description of Mongoid::Enum
133
+ def enum(definitions)
134
+ klass = self
135
+ enum_prefix = definitions.delete(:_prefix)
136
+ enum_suffix = definitions.delete(:_suffix)
137
+ default_key = definitions.delete(:_default)
138
+
139
+ definitions.each do |name, values|
140
+ enum_values = ActiveSupport::HashWithIndifferentAccess.new
141
+ name = name.to_sym
142
+ const_name = name.to_s.pluralize.upcase
143
+
144
+ if klass.const_defined?(const_name)
145
+ fail ArgumentError, "Defining enum :#{name} on #{klass} would " \
146
+ "overwrite existing constant #{klass}::#{const_name}"
147
+ end
148
+
149
+ detect_enum_conflict!(name, name)
150
+ detect_enum_conflict!(name, "#{name}=")
151
+
152
+ if values.respond_to? :each_pair
153
+ values.each_pair { |key, value| enum_values[key.to_s] = value }
154
+ else
155
+ values.each { |v| enum_values[v.to_s] = v.to_s }
156
+ end
157
+
158
+ enum_values.each do |key, value|
159
+ key.freeze
160
+ value.freeze
161
+ end
162
+ enum_values.freeze
163
+
164
+ if default_key && !enum_values.key?(default_key)
165
+ fail ArgumentError, "default key #{default_key} is not among enum options"
166
+ end
167
+
168
+ field name, type: EnumType.new(enum_values), default: default_key
169
+
170
+ klass.const_set const_name, enum_values
171
+ klass.validates name,
172
+ inclusion: {
173
+ in: enum_values.keys,
174
+ allow_nil: true,
175
+ message: "is invalid"
176
+ }
177
+
178
+ _enum_methods_module.module_eval do
179
+ enum_values.each do |key, value|
180
+ if enum_prefix == true
181
+ prefix = "#{name}_"
182
+ elsif enum_prefix
183
+ prefix = "#{enum_prefix}_"
184
+ end
185
+ if enum_suffix == true
186
+ suffix = "_#{name}"
187
+ elsif enum_suffix
188
+ suffix = "_#{enum_suffix}"
189
+ end
190
+
191
+ value_method_name = "#{prefix}#{key}#{suffix}"
192
+
193
+ # def active?() status == 0 end
194
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
195
+ define_method("#{value_method_name}?") { self[name] == value }
196
+
197
+ # def active!() update! status: :active end
198
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
199
+ define_method("#{value_method_name}!") { update! name => key }
200
+
201
+ # scope :active, -> { where status: 0 }
202
+ klass.send(:detect_enum_conflict!, name, value_method_name, true)
203
+ klass.scope value_method_name, -> { klass.where name => key }
204
+ end
205
+ end
206
+
207
+ # dup so children classes don't add their own enums to parent definitions
208
+ self.enums = enums.dup
209
+
210
+ enums[name] = enum_values
211
+ enums.freeze
212
+ end
213
+ end
214
+
215
+ private
216
+
217
+ def _enum_methods_module
218
+ @_enum_methods_module ||= begin
219
+ mod = Module.new
220
+ include mod
221
+ mod
222
+ end
223
+ end
224
+
225
+ # :nodoc:
226
+ ENUM_CONFLICT_MESSAGE = \
227
+ "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
228
+ "this will generate %{type} method \"%{method}\", which is already defined."
229
+
230
+ def detect_enum_conflict!(enum_name, method_name, class_method = false)
231
+ method_name = method_name.to_sym
232
+
233
+ if class_method
234
+ if self.respond_to?(method_name, true)
235
+ raise_conflict_error(enum_name, method_name, "class")
236
+ end
237
+ else
238
+ if Mongoid.destructive_fields.include?(method_name) ||
239
+ instance_methods.include?(method_name)
240
+ raise_conflict_error(enum_name, method_name, "instance")
241
+ end
242
+ end
243
+ end
244
+
245
+ def raise_conflict_error(enum_name, method_name, type)
246
+ fail ArgumentError, ENUM_CONFLICT_MESSAGE % {
247
+ enum: enum_name,
248
+ klass: name,
249
+ type: type,
250
+ method: method_name
251
+ }
252
+ end
253
+ end
254
+ end
255
+ end
@@ -0,0 +1 @@
1
+ require "mongoid/enum"
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid_enum
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Wróbel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mongoid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: factory_girl
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.35.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.35.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: rdoc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.2'
69
+ description: Fields with closed set of possible values and helper methods to query/set
70
+ them by label.
71
+ email: adam@adamwrobel.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - README.md
76
+ files:
77
+ - LICENSE
78
+ - README.md
79
+ - lib/mongoid/enum.rb
80
+ - lib/mongoid/enum/enum_type.rb
81
+ - lib/mongoid/enum/invalid_key.rb
82
+ - lib/mongoid/enum/invalid_value.rb
83
+ - lib/mongoid_enum.rb
84
+ homepage: https://github.com/amw/mongoid_enum
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options:
90
+ - "--main"
91
+ - README.md
92
+ - "--markup=tomdoc"
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 1.9.3
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.4.5.1
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Enum fields for Mongoid
111
+ test_files: []
112
+ has_rdoc: