enum_accessor 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +33 -30
- data/lib/enum_accessor/version.rb +1 -1
- data/lib/enum_accessor.rb +16 -10
- data/spec/enum_accessor_spec.rb +7 -1
- data/spec/locales.yml +7 -4
- metadata +1 -1
data/README.md
CHANGED
@@ -12,14 +12,6 @@ Add this line to your application's Gemfile.
|
|
12
12
|
gem 'enum_accessor'
|
13
13
|
```
|
14
14
|
|
15
|
-
Add an integer column.
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
create_table :users do |t|
|
19
|
-
t.integer :gender, default: 0
|
20
|
-
end
|
21
|
-
```
|
22
|
-
|
23
15
|
Define `enum_accessor` in a model class.
|
24
16
|
|
25
17
|
```ruby
|
@@ -44,13 +36,23 @@ User.genders # => { :female => 0, :male => 1 }
|
|
44
36
|
User::GENDERS # => { "female" => 0, "male" => 1 }
|
45
37
|
```
|
46
38
|
|
47
|
-
Notice that zero-based numbering is used
|
39
|
+
Notice that zero-based numbering is used as database values.
|
40
|
+
|
41
|
+
Your migration should look like this.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
create_table :users do |t|
|
45
|
+
t.integer :gender, :default => 0
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Optionally, it would be a good idea to add `:limit => 1` on the column for even better space efficiency when the enum set is small.
|
48
50
|
|
49
51
|
## Manual coding
|
50
52
|
|
51
|
-
There are times when it makes more sense to manually pick particular
|
53
|
+
There are times when it makes more sense to manually pick particular integer values for the mapping.
|
52
54
|
|
53
|
-
|
55
|
+
In such cases, just pass a hash with coded integer values.
|
54
56
|
|
55
57
|
```ruby
|
56
58
|
enum_accessor :status, ok: 200, not_found: 404, internal_server_error: 500
|
@@ -58,7 +60,7 @@ enum_accessor :status, ok: 200, not_found: 404, internal_server_error: 500
|
|
58
60
|
|
59
61
|
## Scoping query
|
60
62
|
|
61
|
-
|
63
|
+
For querying purpose, use `User.genders` method to retrieve internal integer values.
|
62
64
|
|
63
65
|
```ruby
|
64
66
|
User.where(gender: User.genders(:female))
|
@@ -78,34 +80,37 @@ Or skip validation entirely.
|
|
78
80
|
enum_accessor :status, [ :on, :off ], validate: false
|
79
81
|
```
|
80
82
|
|
81
|
-
##
|
83
|
+
## Translation
|
82
84
|
|
83
|
-
EnumAccessor supports i18n just as ActiveModel does.
|
85
|
+
EnumAccessor supports [i18n](http://guides.rubyonrails.org/i18n.html) just as ActiveModel does.
|
84
86
|
|
85
|
-
|
87
|
+
For instance, create a Japanese translation in `config/locales/ja.yml`
|
86
88
|
|
87
89
|
```yaml
|
88
90
|
ja:
|
89
91
|
enum_accessor:
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
male: 男
|
92
|
+
gender:
|
93
|
+
female: 女
|
94
|
+
male: 男
|
94
95
|
```
|
95
96
|
|
96
|
-
and now `human_*`
|
97
|
+
and now `human_*` methods return a translated string. It defaults to `humanize` method nicely as well.
|
97
98
|
|
98
99
|
```ruby
|
99
100
|
I18n.locale = :ja
|
100
|
-
user.human_gender
|
101
|
+
user.human_gender # => '女'
|
102
|
+
User.human_genders # => { :female => '女', :male => '男' }
|
101
103
|
|
102
104
|
I18n.locale = :en
|
103
|
-
user.human_gender
|
105
|
+
user.human_gender # => 'Female'
|
106
|
+
User.human_genders # => { :female => 'Female', :male => 'Male' }
|
104
107
|
```
|
105
108
|
|
106
109
|
## Why enum keys are internally stored as strings rather than symbols?
|
107
110
|
|
108
|
-
Because `params[:gender].to_sym` is dangerous. It could
|
111
|
+
Because `params[:gender].to_sym` is dangerous. It could lead to problems like memory leak, slow symbol table lookup, or even DoS attack. If a user sends random strings for the parameter, it generates uncontrollable number of symbols, which can never be garbage collected, and eventually causes `symbol table overflow (RuntimeError)`, eating up gigabytes of memory.
|
112
|
+
|
113
|
+
We
|
109
114
|
|
110
115
|
For the same reason, `ActiveSupport::HashWithIndifferentAccess` (which is used for `params`) keeps hash keys as string internally.
|
111
116
|
|
@@ -116,16 +121,14 @@ https://github.com/rails/rails/blob/master/activesupport/lib/active_support/hash
|
|
116
121
|
There are tons of similar gems out there. Then why did I bother creating another one myself rather than sending pull requests to one of them? Because each one of them has incompatible design policies than EnumAccessor.
|
117
122
|
|
118
123
|
* [simple_enum](https://github.com/lwe/simple_enum)
|
119
|
-
|
120
|
-
*
|
121
|
-
* Enum values are defined as top-level predicate methods, which could conflict with existing methods. Also you can't define multiple enums to the same model. In some use cases, predicate methods are not necessary and you just want to be on the safe side.
|
124
|
+
* Pretty close to EnumAccessor feature-wise but requires `*_cd` suffix for the database column, which makes AR scopes ugly.
|
125
|
+
* Enum values are defined as top-level predicate methods, which could conflict with existing methods. Also you can't define multiple enums to the same model. In some use cases, predicate methods are not necessary and you just want to be on the safe side.
|
122
126
|
* [enumerated_attribute](https://github.com/jeffp/enumerated_attribute)
|
123
|
-
|
127
|
+
* Top-level predicate methods. Many additional methods are coupled with a specific usage assumption.
|
128
|
+
* [enum_field](https://github.com/jamesgolick/enum_field)
|
129
|
+
* Top-level predicate methods.
|
124
130
|
* [coded_options](https://github.com/jasondew/coded_options)
|
125
|
-
* No support for symbols. Verbose definitions.
|
126
131
|
* [active_enum](https://github.com/adzap/active_enum)
|
127
|
-
* Syntax seems verbose.
|
128
132
|
* [classy_enum](https://github.com/beerlington/classy_enum)
|
129
|
-
* As the name suggests, class-based enum. I wanted something lighter.
|
130
133
|
|
131
134
|
Also, EnumAccessor has one of the simplest code base, so that you can easily hack on.
|
data/lib/enum_accessor.rb
CHANGED
@@ -26,7 +26,7 @@ module EnumAccessor
|
|
26
26
|
|
27
27
|
# Getter
|
28
28
|
define_method(field) do
|
29
|
-
|
29
|
+
symbolized_enums.key(read_attribute(field))
|
30
30
|
end
|
31
31
|
|
32
32
|
# Setter
|
@@ -52,11 +52,17 @@ module EnumAccessor
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
# Class method
|
55
56
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
56
|
-
def self.#{field.pluralize}(
|
57
|
-
return #{symbolized_enums} if
|
58
|
-
return #{symbolized_enums}[
|
59
|
-
|
57
|
+
def self.#{field.pluralize}(symbol = nil)
|
58
|
+
return #{symbolized_enums} if symbol.nil?
|
59
|
+
return #{symbolized_enums}[symbol]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.human_#{field.pluralize}(symbol = nil)
|
63
|
+
humanized_enums = Hash[#{symbolized_enums}.map{|k,v| [k, human_enum_accessor(:#{field}, k)] }]
|
64
|
+
return humanized_enums if symbol.nil?
|
65
|
+
return humanized_enums[symbol]
|
60
66
|
end
|
61
67
|
EOS
|
62
68
|
|
@@ -72,14 +78,14 @@ module EnumAccessor
|
|
72
78
|
end
|
73
79
|
|
74
80
|
# Mimics ActiveModel::Translation.human_attribute_name
|
75
|
-
def human_enum_accessor(field,
|
81
|
+
def human_enum_accessor(field, key, options = {})
|
76
82
|
defaults = lookup_ancestors.map do |klass|
|
77
|
-
:"#{self.i18n_scope}.enum_accessor.#{klass.model_name.i18n_key}.#{field}.#{
|
83
|
+
:"#{self.i18n_scope}.enum_accessor.#{klass.model_name.i18n_key}.#{field}.#{key}"
|
78
84
|
end
|
79
|
-
defaults << :"enum_accessor.#{self.model_name.i18n_key}.#{field}.#{
|
80
|
-
defaults << :"enum_accessor.#{field}.#{
|
85
|
+
defaults << :"enum_accessor.#{self.model_name.i18n_key}.#{field}.#{key}"
|
86
|
+
defaults << :"enum_accessor.#{field}.#{key}"
|
81
87
|
defaults << options.delete(:default) if options[:default]
|
82
|
-
defaults <<
|
88
|
+
defaults << key.to_s.humanize
|
83
89
|
|
84
90
|
options.reverse_merge! :count => 1, :default => defaults
|
85
91
|
I18n.translate(defaults.shift, options)
|
data/spec/enum_accessor_spec.rb
CHANGED
@@ -38,12 +38,18 @@ describe EnumAccessor do
|
|
38
38
|
@user.gender_male?.should == true
|
39
39
|
end
|
40
40
|
|
41
|
-
it 'adds humanized
|
41
|
+
it 'adds humanized methods' do
|
42
42
|
I18n.locale = :ja
|
43
|
+
User.human_attribute_name(:gender).should == '性別'
|
43
44
|
@user.human_gender.should == '女'
|
45
|
+
User.human_genders(:female).should == '女'
|
46
|
+
User.human_genders.should == { :female => '女', :male => '男' }
|
44
47
|
|
45
48
|
I18n.locale = :en
|
49
|
+
User.human_attribute_name(:gender).should == 'Gender'
|
46
50
|
@user.human_gender.should == 'Female'
|
51
|
+
User.human_genders(:female).should == 'Female'
|
52
|
+
User.human_genders.should == { :female => 'Female', :male => 'Male' }
|
47
53
|
end
|
48
54
|
|
49
55
|
it 'defines internal constant' do
|
data/spec/locales.yml
CHANGED