magic_enum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009-2012, Dmytro Shteflyuk, Oleksiy Kovyrin
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
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,79 @@
1
+ = MagicEnum
2
+
3
+ MagicEnum is a simple ActiveRecord plugin that makes it easier to maintain ENUM-like attributes in your models.
4
+
5
+ Examples:
6
+
7
+ Statuses = {
8
+ :draft => 0,
9
+ :published => 1,
10
+ :approved => 2
11
+ }
12
+ define_enum :status
13
+
14
+ == How to Use
15
+
16
+ Before using <tt>define_enum</tt>, you should define constant with ENUM options.
17
+ Constant name would be pluralized enum attribute name. Additional constant named
18
+ like YourEnumInverted would be created automatically and would contain
19
+ inverted hash.
20
+
21
+ <b>Please note</b>: nil and 0 are not the same values!
22
+
23
+ You could specify additional options:
24
+
25
+ * <tt>:default</tt> - value which will be used when current state of ENUM attribute is invalid or wrong value received. If it has not been specified, min value of the ENUM would be used.
26
+ * <tt>:raise_on_invalid</tt> - if +true+ an exception would be raised on invalid enum value received. If it is +false+, default value would be used instead of wrong one.
27
+ * <tt>:simple_accessors</tt> - if +true+, additional methods for each ENUM value would be defined in form value?. Methods returns +true+ when ENUM attribute has corresponding value.
28
+ * <tt>:enum</tt> - string with name of the ENUM hash.
29
+
30
+ Look the following example:
31
+
32
+ Statuses = {
33
+ :unknown => 0,
34
+ :draft => 1,
35
+ :published => 2,
36
+ :approved => 3
37
+ }
38
+ define_enum :status, :default => 1, :raise_on_invalid => true, :simple_accessors => true
39
+
40
+ This example is identical to:
41
+
42
+ Statuses = {
43
+ :unknown => 0,
44
+ :draft => 1,
45
+ :published => 2,
46
+ :approved => 3
47
+ }
48
+ StatusesInverted = Statuses.invert
49
+
50
+ def status
51
+ return StatusesInverted[self[:status].to_i] || StatusesInverted[1]
52
+ end
53
+
54
+ def status=(value)
55
+ raise ArgumentError, "Invalid value \"#{value}\" for :status attribute of the #{self.class} model" if Statuses[value].nil?
56
+ self[:status] = Statuses[value]
57
+ end
58
+
59
+ def unknown?
60
+ status == :unknown
61
+ end
62
+
63
+ def draft?
64
+ status == :draft
65
+ end
66
+
67
+ def published?
68
+ status == :published
69
+ end
70
+
71
+ def approved?
72
+ status == :approved
73
+ end
74
+
75
+ == Who are the authors?
76
+
77
+ This plugin was originally developed for BestTechVideos project (http://www.bestechvideos.com) by Dmytro Shteflyuk (http://kpumuk.info)
78
+ and later cleaned up in Scribd repository and released to the public by Oleksiy Kovyrin. All the code in this package is released under
79
+ the MIT license. For more details, see the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ require 'rdoc/task'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :spec
10
+
11
+ desc 'Test the magic_enum plugin.'
12
+ RSpec::Core::RakeTask.new(:spec)
13
+
14
+ desc 'Generate documentation for the magic_enum plugin.'
15
+ Rake::RDocTask.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'MagicEnum'
18
+ rdoc.options << '--line-numbers' << '--inline-source'
19
+ rdoc.rdoc_files.include('README')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/lib/magic_enum'
2
+
3
+ ActiveRecord::Base.send(:include, MagicEnum)
@@ -0,0 +1,11 @@
1
+ # Version info
2
+ module MagicEnum
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ PATCH = 1
7
+ BUILD = nil
8
+
9
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
10
+ end
11
+ end
data/lib/magic_enum.rb ADDED
@@ -0,0 +1,154 @@
1
+ module MagicEnum
2
+ def self.append_features(base) #:nodoc:
3
+ super
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ # Method used to define ENUM attributes in your model. Examples:
9
+ #
10
+ # Statuses = {
11
+ # :draft => 0,
12
+ # :published => 1,
13
+ # :approved => 2
14
+ # }
15
+ # define_enum :status
16
+ #
17
+ # Before using <tt>define_enum</tt>, you should define constant with ENUM options.
18
+ # Constant name would be pluralized enum attribute name. Additional constant named
19
+ # like <tt>YourEnumInverted</tt> would be created automatically and would contain
20
+ # inverted hash.
21
+ #
22
+ # <b>Please note</b>: <tt>nil</tt> and <tt>0</tt> are not the same values!
23
+ #
24
+ # You could specify additional options:
25
+ #
26
+ # * <tt>:default</tt> - value which will be used when current state of ENUM attribute is
27
+ # invalid or wrong value received. If it has not been specified, min value of the ENUM would
28
+ # be used.
29
+ # * <tt>:raise_on_invalid</tt> - if <tt>true</tt> an exception would be raised on invalid
30
+ # enum value received. If it is <tt>false</tt>, default value would be used instead of
31
+ # wrong one.
32
+ # * <tt>:simple_accessors</tt> - if <tt>true</tt>, additional methods for each ENUM value
33
+ # would be defined in form <tt>value?</tt>. Methods returns <tt>true</tt> when
34
+ # ENUM attribute has corresponding value.
35
+ # * <tt>:enum</tt> - string with name of the ENUM hash.
36
+ #
37
+ # Look the following example:
38
+ #
39
+ # Statuses = {
40
+ # :unknown => 0,
41
+ # :draft => 1,
42
+ # :published => 2,
43
+ # :approved => 3
44
+ # }
45
+ # define_enum :status, :default => 1, :raise_on_invalid => true, :simple_accessors => true
46
+ #
47
+ # This example is identical to:
48
+ #
49
+ # Statuses = {
50
+ # :unknown => 0,
51
+ # :draft => 1,
52
+ # :published => 2,
53
+ # :approved => 3
54
+ # }
55
+ # StatusesInverted = Statuses.invert
56
+ #
57
+ # def status
58
+ # return StatusesInverted[self[:status].to_i] || StatusesInverted[1]
59
+ # end
60
+ #
61
+ # def status=(value)
62
+ # raise ArgumentError, "Invalid value \"#{value}\" for :status attribute of the #{self.class} model" if Statuses[value].nil?
63
+ # self[:status] = Statuses[value]
64
+ # end
65
+ #
66
+ # def unknown?
67
+ # status == :unknown
68
+ # end
69
+ #
70
+ # def draft?
71
+ # status == :draft
72
+ # end
73
+ #
74
+ # def published?
75
+ # status == :published
76
+ # end
77
+ #
78
+ # def approved?
79
+ # status == :approved
80
+ # end
81
+ #
82
+ def define_enum(name, opts = {})
83
+ default_opts = { :raise_on_invalid => false,
84
+ :simple_accessors => false,
85
+ :named_scopes => false,
86
+ :scope_extensions => false
87
+ }
88
+ opts = default_opts.merge(opts)
89
+ name = name.to_sym
90
+
91
+ # bug in Rails 1.2.2
92
+ opts[:enum] = name.to_s.pluralize.classify.pluralize unless opts[:enum]
93
+ enum = opts[:enum]
94
+ enum_inverted = "#{enum}Inverted"
95
+
96
+ opts[:default] = const_get(enum).values.sort do |a, b|
97
+ if a.nil? and b.nil?
98
+ 0
99
+ elsif a.nil?
100
+ -1
101
+ elsif b.nil?
102
+ 1
103
+ else
104
+ a <=> b
105
+ end
106
+ end.first unless opts[:default]
107
+
108
+ const_set(enum_inverted, const_get(enum).invert)
109
+
110
+ define_method name do
111
+ self.class.const_get(enum_inverted)[self[name]] || self.class.const_get(enum_inverted)[opts[:default]]
112
+ end
113
+
114
+ define_method "#{name}_name" do
115
+ send(name).to_s
116
+ end
117
+
118
+ define_method "#{name}=" do |value|
119
+ value = value.to_sym if value.is_a?(String)
120
+ raise ArgumentError, "Invalid value \"#{value}\" for :#{name} attribute of the #{self.class} model" if opts[:raise_on_invalid] and self.class.const_get(enum)[value].nil?
121
+ if value.is_a?(Integer)
122
+ self[name] = value
123
+ else
124
+ self[name] = self.class.const_get(enum)[value] || opts[:default]
125
+ end
126
+ end
127
+
128
+ if opts[:simple_accessors]
129
+ const_get(enum).keys.each do |key|
130
+ define_method "#{key}?" do
131
+ send(name) == key
132
+ end
133
+ end
134
+ end
135
+
136
+ # Create named scopes for each enum value
137
+ if opts[:named_scopes]
138
+ const_get(enum).keys.each do |key|
139
+ named_scope key.to_s.pluralize.to_sym, :conditions => ["#{name} = ?", const_get(enum)[key]] do
140
+ opts[:scope_extensions].each do |ext_name, ext_block|
141
+ define_method ext_name, ext_block
142
+ end if opts[:scope_extensions] and opts[:scope_extensions].is_a?(Hash)
143
+ end
144
+ end
145
+ named_scope "of_#{name}".to_sym, lambda { |t| { :conditions => ["#{name} = ?", const_get(enum)[t]] } } do
146
+ opts[:scope_extensions].each do |ext_name, ext_block|
147
+ define_method ext_name, ext_block
148
+ end if opts[:scope_extensions] and opts[:scope_extensions].is_a?(Hash)
149
+ end
150
+ end
151
+
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'magic_enum/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'magic_enum'
7
+ s.version = MagicEnum::Version::STRING
8
+ s.platform = Gem::Platform::RUBY
9
+
10
+ s.authors = [ 'Dmytro Shteflyuk', 'Oleksiy Kovyrin' ]
11
+ s.email = 'alexey@kovyrin.net'
12
+ s.homepage = 'https://github.com/kovyrin/magic-enum'
13
+ s.summary = 'ActiveRecord plugin that makes it easier to maintain ENUM-like attributes in your models'
14
+ s.description = 'MagicEnum is a simple ActiveRecord plugin that makes it easier to maintain ENUM-like attributes in your models.'
15
+
16
+ s.rdoc_options = [ '--charset=UTF-8' ]
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.require_paths = [ 'lib' ]
20
+ s.extra_rdoc_files = [ 'LICENSE', 'README.rdoc' ]
21
+
22
+ # Dependencies
23
+ s.add_dependency 'activerecord', '<= 3.2'
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'yard'
26
+ end
@@ -0,0 +1,221 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+
4
+ require File.dirname(__FILE__) + '/../init'
5
+
6
+ module MagicEnumHelper
7
+ class TestModelBase
8
+ include MagicEnum
9
+ Statuses = { :unknown => 0, :draft => 1, :published => 2 }
10
+
11
+ def [](attr_name)
12
+ @status
13
+ end
14
+
15
+ def []=(attr_name, value)
16
+ @status = value
17
+ end
18
+ end
19
+ end
20
+
21
+ describe 'Model with magic enum' do
22
+ include MagicEnumHelper
23
+
24
+ class TestModel1 < MagicEnumHelper::TestModelBase
25
+ define_enum :status
26
+ end
27
+
28
+ before do
29
+ @model = TestModel1.new
30
+ end
31
+
32
+ specify 'should define methods to get and set enum field' do
33
+ TestModel1.should be_method_defined(:status)
34
+ TestModel1.should be_method_defined(:status=)
35
+ end
36
+
37
+ specify 'should store enum value using [] operation on model' do
38
+ @model.status = :draft
39
+ @model[:status].should == 1
40
+ @model.status.should == :draft
41
+ @model.status = :unknown
42
+ @model[:status].should == 0
43
+ @model.status.should == :unknown
44
+ end
45
+
46
+ specify 'should store enum value if key is given as string' do
47
+ @model.status = 'draft'
48
+ @model[:status].should == 1
49
+ @model.status.should == :draft
50
+ @model.status = 'unknown'
51
+ @model[:status].should == 0
52
+ @model.status.should == :unknown
53
+ end
54
+
55
+ specify 'should store enum value if key is given as integer equivalent' do
56
+ @model.status = 1
57
+ @model[:status].should == 1
58
+ @model.status.should == :draft
59
+ @model.status = 0
60
+ @model[:status].should == 0
61
+ @model.status.should == :unknown
62
+ end
63
+
64
+ specify 'should not define simple accessors by default' do
65
+ @model.methods.should_not include('unknown?')
66
+ @model.methods.should_not include('draft?')
67
+ @model.methods.should_not include('published?')
68
+ end
69
+
70
+ specify 'should not raise error when invalid value received' do
71
+ lambda { @model.status = :invalid }.should_not raise_error(ArgumentError)
72
+ end
73
+
74
+ specify 'should use default value 0 when invalid value received or current state invalid' do
75
+ @model[:status] = -1
76
+ @model.status.should == :unknown
77
+ @model.status = :published
78
+ @model.status.should == :published
79
+ @model.status = :invalid
80
+ @model[:status].should == 0
81
+ @model.status.should == :unknown
82
+ end
83
+
84
+ specify 'should return string value when _name method called' do
85
+ @model.status_name.should == 'unknown'
86
+ @model.status = :published
87
+ @model.status_name.should == 'published'
88
+ end
89
+
90
+ specify 'should not define named scopes by default' do
91
+ TestModel1.should_not_receive(:named_scope)
92
+ end
93
+
94
+ end
95
+
96
+ describe 'Model with magic enum and default value specified' do
97
+ include MagicEnumHelper
98
+
99
+ class TestModel2 < MagicEnumHelper::TestModelBase
100
+ define_enum :status, :default => 2
101
+ end
102
+
103
+ before do
104
+ @model = TestModel2.new
105
+ end
106
+
107
+ specify 'should use default value when current state is invalid' do
108
+ @model[:status] = -1
109
+ @model.status.should == :published
110
+ end
111
+
112
+ specify 'should use default value when invalid value received' do
113
+ @model.status = nil
114
+ @model.status.should == :published
115
+ @model.status = :invalid
116
+ @model.status.should == :published
117
+ @model[:status].should == 2
118
+ end
119
+
120
+ specify 'should not interpret nil in the same way as 0' do
121
+ @model[:status].should be_nil
122
+ @model.status.should == :published
123
+ @model[:status] = 0
124
+ @model.status.should == :unknown
125
+ end
126
+ end
127
+
128
+ describe 'Model with magic enum and raise_on_invalid option specified' do
129
+ include MagicEnumHelper
130
+
131
+ class TestModel3 < MagicEnumHelper::TestModelBase
132
+ define_enum :status, :raise_on_invalid => true
133
+ end
134
+
135
+ before do
136
+ @model = TestModel3.new
137
+ end
138
+
139
+ specify 'should raise error when invalid value received' do
140
+ lambda { @model.status = :invalid }.should raise_error(ArgumentError)
141
+ end
142
+
143
+ specify 'should show error description when invalid value received' do
144
+ begin
145
+ @model.status = :invalid
146
+ rescue => e
147
+ e.message.should == 'Invalid value "invalid" for :status attribute of the TestModel3 model'
148
+ end
149
+ end
150
+ end
151
+
152
+
153
+ describe 'Model with magic enum and simple_accessors option specified' do
154
+ include MagicEnumHelper
155
+
156
+ class TestModel4 < MagicEnumHelper::TestModelBase
157
+ define_enum :status, :simple_accessors => true
158
+ end
159
+
160
+ before do
161
+ @model = TestModel4.new
162
+ end
163
+
164
+ specify 'should define simple accessors by default' do
165
+ @model.methods.should include('unknown?')
166
+ @model.methods.should include('draft?')
167
+ @model.methods.should include('published?')
168
+ end
169
+ end
170
+
171
+ describe 'Model with magic enum and named_scopes option specified' do
172
+ include MagicEnumHelper
173
+
174
+ class TestModel5 < MagicEnumHelper::TestModelBase
175
+ end
176
+
177
+ specify 'should define named_scopes' do
178
+ TestModel5.should_receive(:named_scope).with(:unknowns, {:conditions=>["status = ?", 0]}).once
179
+ TestModel5.should_receive(:named_scope).with(:drafts, {:conditions=>["status = ?", 1]}).once
180
+ TestModel5.should_receive(:named_scope).with(:publisheds, {:conditions=>["status = ?", 2]}).once
181
+ TestModel5.should_receive(:named_scope).once # to handle the :of_status named_scope,
182
+ # since we can't set an expectation with a Proc argument
183
+ TestModel5.send(:define_enum, :status, :named_scopes => true)
184
+ end
185
+ end
186
+
187
+ describe 'Model with magic enum and enum option specified' do
188
+ include MagicEnumHelper
189
+
190
+ class TestModel7 < MagicEnumHelper::TestModelBase
191
+ Roles = {
192
+ :user => 'u',
193
+ :admin => 'a'
194
+ }
195
+ define_enum :status, :enum => 'Roles'
196
+ end
197
+
198
+ before do
199
+ @model = TestModel7.new
200
+ end
201
+
202
+ specify 'should use custom enum' do
203
+ @model.status = :user
204
+ @model.status.should == :user
205
+ @model[:status].should == 'u'
206
+ @model.status = :admin
207
+ @model.status.should == :admin
208
+ @model[:status].should == 'a'
209
+ end
210
+
211
+ specify 'should use option with min value as default' do
212
+ @model.status = :invalid
213
+ @model.status.should == :admin
214
+ end
215
+ end
216
+
217
+ describe 'ActiveRecord::Base class' do
218
+ specify 'should include MagicEnum module' do
219
+ ActiveRecord::Base.included_modules.should include(MagicEnum)
220
+ end
221
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magic_enum
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Dmytro Shteflyuk
14
+ - Oleksiy Kovyrin
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-01-30 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - <=
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 3
32
+ - 2
33
+ version: "3.2"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: yard
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ description: MagicEnum is a simple ActiveRecord plugin that makes it easier to maintain ENUM-like attributes in your models.
65
+ email: alexey@kovyrin.net
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files:
71
+ - LICENSE
72
+ - README.rdoc
73
+ files:
74
+ - LICENSE
75
+ - README.rdoc
76
+ - Rakefile
77
+ - init.rb
78
+ - lib/magic_enum.rb
79
+ - lib/magic_enum/version.rb
80
+ - magic_enum.gemspec
81
+ - spec/magic_enum_spec.rb
82
+ homepage: https://github.com/kovyrin/magic-enum
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options:
87
+ - --charset=UTF-8
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.10
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: ActiveRecord plugin that makes it easier to maintain ENUM-like attributes in your models
115
+ test_files: []
116
+