has_enum 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ pkg/*
6
6
  spec/*.db
7
7
  *.swp
8
8
  Gemfile.lock
9
+ bin
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "http://rubygems.org"
1
+ source "http://gems.openteam.ru"
2
2
 
3
3
  gemspec
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
6
6
  s.name = "has_enum"
7
7
  s.version = HasEnum::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Andreas Korth", "Konstantin Shabanov"]
10
- s.email = ["kes.eclipse@gmail.com"]
9
+ s.authors = ["Andreas Korth", "Konstantin Shabanov", "Dmitry Lihachev"]
10
+ s.email = ["lda@openteam.ru"]
11
11
  s.homepage = "http://github.com/openteam/has_enum"
12
12
  s.summary = %q{Gem for Rails to easily handle enumeration attributes in models}
13
13
  s.description = %q{Gem for and Rails3 to easily handle enumeration attributes in ActiveRecord's models}
@@ -1,4 +1,40 @@
1
- require 'has_enum/active_record'
2
- require 'has_enum/helpers'
1
+ module HasEnum
3
2
 
4
- ActiveRecord::Base.send(:include, HasEnum::ActiveRecord)
3
+ autoload :ClassMethods, 'has_enum/class_methods'
4
+
5
+ def self.included(base)
6
+ base.write_inheritable_attribute(:enums, HashWithIndifferentAccess.new)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ end
11
+
12
+ class ActiveRecord::Base
13
+ include HasEnum
14
+ end
15
+
16
+ begin
17
+ require 'formtastic'
18
+
19
+ Formtastic::SemanticFormBuilder.class_eval do
20
+
21
+ def enum_input(method, options = {})
22
+ value = @object.send(method)
23
+ options.reverse_merge! :as => :select,
24
+ :collection => @object.class.human_enums[method].invert.to_a
25
+ self.input(method, options).gsub(/class="select/, 'class="enum')
26
+ end
27
+
28
+ def input_with_enum(method, options={})
29
+ if @object.class.respond_to?(:enums) && @object.class.enums[method] && !options[:as]
30
+ enum_input(method, options)
31
+ else
32
+ input_without_enum(method, options)
33
+ end
34
+ end
35
+
36
+ alias_method_chain :input, :enum
37
+
38
+ end
39
+ rescue LoadError
40
+ end
@@ -0,0 +1,79 @@
1
+ module HasEnum::ClassMethods
2
+
3
+ def enums
4
+ @enums ||= read_inheritable_attribute(:enums)
5
+ end
6
+
7
+ def enum_values(attribute)
8
+ enums[attribute]
9
+ end
10
+
11
+ def has_enum(*params)
12
+ options = params.extract_options!
13
+ options.assert_valid_keys(:query_methods, :scopes, :presence)
14
+
15
+ raise ArgumentError, "Empty arguments list for has_enum call" if params.empty?
16
+
17
+ default_values = nil
18
+ case params.second
19
+ when NilClass # has_enum :enum
20
+ attributes = [*params.first]
21
+ when Array # has_enum :enum, %w[foo bar]
22
+ attributes = [*params.first]
23
+ default_values = params.second.map(&:to_s)
24
+ else # has_enum :state, :status
25
+ attributes = params
26
+ end
27
+
28
+ attributes.map(&:to_sym).each do | attribute |
29
+ values = default_values || human_enum_values(attribute).keys.map(&:to_s)
30
+
31
+ enums[attribute] = values
32
+
33
+ validates attribute, :inclusion => { :in => values + [nil]}
34
+ validates attribute, :presence => options[:presence] if options[:presence]
35
+
36
+ values.each do |val|
37
+ scope "#{attribute}_#{val}", where(attribute => val)
38
+ end if options[:scopes]
39
+
40
+ values.each do |val|
41
+ define_method "#{attribute}_#{val}?" do
42
+ self.send(attribute) == val.to_s
43
+ end
44
+ end if options[:query_methods] != false
45
+
46
+ define_method "human_#{attribute}" do
47
+ self.class.human_enums[attribute][self[attribute]]
48
+ end
49
+
50
+ define_method "#{attribute}=" do | value |
51
+ value = value.to_s
52
+ value = nil if value == ''
53
+ self[attribute] = value
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ def human_enums
60
+ @human_enums ||= enums.keys.inject HashWithIndifferentAccess.new do | hash, enum |
61
+ hash[enum] = human_enum_values enum
62
+ hash
63
+ end
64
+ end
65
+
66
+ def human_enum_values(enum)
67
+ begin
68
+ options = {:default => nil, :raise => true, :count => nil}
69
+ HashWithIndifferentAccess.new(human_attribute_name("#{enum}_enum", options))
70
+ rescue I18n::MissingTranslationData
71
+ (enums[enum] || []).inject HashWithIndifferentAccess.new do |hash, value|
72
+ hash[value] = value.humanize
73
+ hash
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+
@@ -1,10 +1,12 @@
1
1
  module ActionView::Helpers::FormHelper
2
2
  def radio_button_enum(object_name, method, options = {})
3
- ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_enum_tag(options)
3
+ ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).
4
+ to_radio_button_enum_tag(options)
4
5
  end
5
6
 
6
7
  def select_enum(object_name, method, options = {})
7
- ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).to_select_enum_tag(options)
8
+ ActionView::Helpers::InstanceTag.new(object_name, method, self, options.delete(:object)).
9
+ to_select_enum_tag(options)
8
10
  end
9
11
  end
10
12
 
@@ -13,7 +15,7 @@ class ActionView::Helpers::InstanceTag
13
15
  def to_radio_button_enum_tag(options = {})
14
16
  values_for_enum_tag.map do |val|
15
17
  radio_button = to_radio_button_tag(val.last, options)
16
- [ radio_button, to_label_tag(val.first, :for => radio_button.match(/ id="(.*?)"/)[1]) ] * $/
18
+ [radio_button, to_label_tag(val.first, :for => radio_button.match(/ id="(.*?)"/)[1])] * $/
17
19
  end.join($/)
18
20
  end
19
21
 
@@ -23,13 +25,7 @@ class ActionView::Helpers::InstanceTag
23
25
  end
24
26
 
25
27
  def values_for_enum_tag
26
- values = object.class.enum(method_name.to_sym)
27
- begin
28
- translation = I18n.translate("activerecord.attributes.#{object.class.name.i18nize}.#{method_name}_enum", :raise => true)
29
- values.map { |val| [translation[val.to_sym], val] }
30
- rescue I18n::MissingTranslationData
31
- values.map { |val| [val.humanize, val] }
32
- end
28
+ object.class.human_enums[method_name].invert.to_a
33
29
  end
34
30
  end
35
31
 
@@ -42,4 +38,5 @@ class ActionView::Helpers::FormBuilder
42
38
  def select_enum(method, options = {})
43
39
  @template.select_enum(@object_name, method, objectify_options(options.merge(:include_blank => true)))
44
40
  end
45
- end
41
+ end
42
+
@@ -1,3 +1,3 @@
1
1
  module HasEnum
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -2,149 +2,192 @@
2
2
 
3
3
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
4
 
5
- describe HasEnum::ActiveRecord do
6
- before :each do
7
- @model = TestModel.new(:category => 'misc', :color => 'blue', :foo => 'bar', :status => :pending)
5
+ describe HasEnum do
6
+ let :model do
7
+ TestModel.new(:category => :stuff, :status => :pending, :state => :done)
8
+ end
9
+
10
+ let :human_enums do
11
+ {
12
+ :category => {
13
+ :stuff => 'Stuff',
14
+ :things => 'Things',
15
+ :misc => 'Misc'
16
+ },
17
+ :color => {
18
+ :red => 'Red',
19
+ :green => 'Green',
20
+ :blue => 'Blue'
21
+ },
22
+ :size => {
23
+ :small => "Маленький",
24
+ :medium => "Средний",
25
+ :large => "Большой"
26
+ },
27
+ :status => {
28
+ :pending => 'На рассмотрении',
29
+ :failed => "Обработано с ошибкой",
30
+ :done => "Завершено"
31
+ },
32
+ :state => {
33
+ :pending => 'Pending',
34
+ :failed => 'Failed',
35
+ :done => 'Done'
36
+ },
37
+ }.with_indifferent_access
38
+ end
39
+
40
+ it "should return values by name symbol" do
41
+ TestModel.enums[:state].should eql human_enums[:state].stringify_keys.keys
42
+ end
43
+
44
+ it "should return values by name string" do
45
+ TestModel.enums["state"].should eql human_enums[:state].stringify_keys.keys
8
46
  end
9
47
 
10
48
  it "should return the values for a given enum attribute" do
11
- TestModel.enum[:category].should eql(%w(stuff things misc))
49
+ TestModel.enums[:category].should eql human_enums[:category].stringify_keys.keys
12
50
  end
13
51
 
14
52
  it "should return hash of values with it's translated equivalent" do
15
- I18n.reload!
16
- TestModel.human_enum[:size].should eql Hash[:small => "Маленький",
17
- :medium => "Средний",
18
- :large => "Большой"]
19
- TestModel.human_enum[:status].should eql Hash[:pending => "На рассмотрении",
20
- :failed =>"Обработано с ошибкой",
21
- :done =>"Завершено"]
22
- TestModel.human_enum[:category].should eql Hash[:stuff => 'Stuff',
23
- :things => 'Things',
24
- :misc => 'Misc']
53
+ TestModel.human_enums[:size].should eql human_enums[:size]
54
+ TestModel.human_enums[:status].should eql human_enums[:status]
55
+ TestModel.human_enums[:category].should eql human_enums[:category]
25
56
  end
26
-
57
+
27
58
  it "should return hash of enums with hashes of attributes and theirs translated equivalent" do
28
- I18n.reload!
29
- TestModel.human_enum.should eql Hash[:category => {:stuff=>"Stuff",
30
- :things=>"Things",
31
- :misc=>"Misc"},
32
- :color => {:red=>"Red",
33
- :green=>"Green",
34
- :blue=>"Blue"},
35
- :size => {:small=>"Маленький",
36
- :medium=>"Средний",
37
- :large=>"Большой"},
38
- :status => {:pending=>"На рассмотрении",
39
- :failed=>"Обработано с ошибкой",
40
- :done=>"Завершено"}]
59
+ TestModel.human_enums.should eql human_enums
41
60
  end
42
61
 
43
62
  it "should return translated value for attribute" do
44
- I18n.reload!
45
- TestModel.human_enum[:size][:large].should eql "Большой"
46
- TestModel.human_enum[:color][:red].should eql "Red"
47
- TestModel.human_enum[:status][:done].should eql "Завершено"
63
+ TestModel.human_enums[:size][:large].should eql "Большой"
64
+ TestModel.human_enums[:color][:red].should eql "Red"
65
+ TestModel.human_enums[:status][:done].should eql "Завершено"
48
66
  end
49
67
 
50
68
  describe "category enum" do
51
- it "should accept enum values for the attribute" do
69
+ it "should accept string enum values" do
52
70
  %w(stuff things misc).each do |value|
53
- @model.category = value
54
- @model.should be_valid
71
+ model.category = value
72
+ model.should be_valid
55
73
  end
56
74
  end
57
75
 
58
- it "should accept nil value for the attribute" do
59
- @model.category = nil
60
- @model.category.should be_nil
76
+ it "should accept symbol enum values" do
77
+ %w(stuff things misc).each do |value|
78
+ model.category = value.to_sym
79
+ model.should be_valid
80
+ end
61
81
  end
62
82
 
63
- it "should reject non enum values for the attribute" do
64
- @model.category = 'objects'
65
- @model.errors[:category].size.should eql(1)
83
+ it "should reject not enum values" do
84
+ model.category = "not_listed_in_enum"
85
+ model.should_not be_valid
86
+ end
87
+
88
+ it "should accept nil value for the attribute on create" do
89
+ model.category = nil
90
+ model.should be_valid
91
+ end
92
+
93
+ it "should not accept nil value for the attribute on update" do
94
+ model.save
95
+ model.category = nil
96
+ model.should_not be_valid
97
+ end
98
+
99
+ it "should not accept blank value for the attribute" do
100
+ model.category = ' '
101
+ model.should_not be_valid
102
+ end
103
+
104
+ it "should normalize empty value for the attribute" do
105
+ model.category = ''
106
+ model.category.should be_nil
66
107
  end
67
108
 
68
109
  it "should define query methods for enum values" do
69
- %w( stuff things misc ).each do |value|
70
- @model.should respond_to(:"category_#{value}?")
110
+ %w[stuff things misc].each do |value|
111
+ model.should respond_to(:"category_#{value}?")
71
112
  end
72
113
  end
73
- end
74
114
 
75
- describe "color enum" do
76
- it "should accept any value for the attribute" do
77
- %w(red orange yellow).each do |value|
78
- @model.color = value
79
- @model.should be_valid
80
- end
115
+ it "query methods should works" do
116
+ model.category = :stuff
117
+ model.should be_category_stuff
118
+ model.should_not be_category_things
119
+ model.should_not be_category_misc
120
+
121
+ model.category = :things
122
+ model.should_not be_category_stuff
123
+ model.should be_category_things
124
+ model.should_not be_category_misc
125
+
126
+ model.category = :misc
127
+ model.should_not be_category_stuff
128
+ model.should_not be_category_things
129
+ model.should be_category_misc
81
130
  end
82
131
 
83
- it "should reject an empty value for the attribute" do
84
- @model.color = ''
85
- @model.errors[:color].size.should eql(1)
132
+ it "should not define a scope methods for each enum value" do
133
+ TestModel.should_not respond_to :category_stuff
134
+ TestModel.should_not respond_to :category_things
135
+ TestModel.should_not respond_to :category_misc
86
136
  end
87
137
 
88
- it "should define a query method for each enum value" do
89
- @model.color = 'green'
90
- @model.category = 'stuff'
91
- @model.should be_category_stuff
92
- @model.should be_color_green
93
- @model.should_not be_color_red
94
- @model.should_not be_color_blue
138
+ it "should translate category by human_category method" do
139
+ model.human_category.should eql 'Stuff'
95
140
  end
141
+ end
96
142
 
143
+ describe "color enum" do
97
144
  it "should define a scope for each enum value" do
98
- @model.color = 'red'
99
- @model.save
100
- @model2 = TestModel.new(:color => 'red')
101
- @model2.save
145
+ model.color = 'red'
146
+ model.save
147
+
148
+ model2 = model.clone
149
+ model2.color = :blue
150
+ model2.save
102
151
 
103
152
  TestModel.color_red.all.should eql TestModel.where(:color => 'red').all
104
- TestModel.color_green.all.should be_empty
153
+
154
+ TestModel.color_red.should be_one
155
+ TestModel.color_blue.should be_one
156
+ TestModel.color_green.count.should be_zero
105
157
  end
106
- end
107
158
 
108
- describe "size enum" do
109
- it "should accept any value for the attribute" do
110
- ['orange', 'medium', 'misc', '', nil].each do |value|
111
- @model.size = value
112
- @model.should be_valid
113
- end
159
+ it "should accept nil value for the attribute" do
160
+ model.color = nil
161
+ model.should be_valid
162
+ model.save
163
+ model.should be_valid
164
+ end
165
+
166
+ it "should accept blank value for the attribute" do
167
+ model.color = ''
168
+ model.should be_valid
169
+ model.save
170
+ model.should be_valid
114
171
  end
172
+ end
115
173
 
174
+ describe "size enum" do
116
175
  it "should not define query methods for enum values" do
117
176
  %w(small medium large).each do |value|
118
- @model.should_not respond_to(:"size_#{value}?")
177
+ model.should_not respond_to(:"size_#{value}?")
119
178
  end
120
179
  end
121
-
122
- it "should return humanized translation if not localized" do
123
- @model.category = 'stuff'
124
- @model.human_category.should eql("Stuff")
125
- end
126
180
  end
127
181
 
128
182
  describe "status enum" do
129
- it "should accept symbols as symbols" do
130
- @model.status = :pending
131
- @model.status.should eql(:pending)
132
- end
133
-
134
- it "should define query methods for enum values" do
135
- @model.status = :pending
136
- @model.should be_status_pending
137
- end
138
-
139
- it "should accept nil value for the attribute" do
140
- @model.status = nil
141
- @model.status.should be_nil
183
+ it "should not accept nil value for the attribute" do
184
+ model.status = nil
185
+ model.should_not be_valid
142
186
  end
143
187
 
144
- it "should translate values for the enum" do
145
- I18n.reload!
146
- @model.status = :pending
147
- @model.human_status.should eql("На рассмотрении")
188
+ it "should not accept blank value for the attribute" do
189
+ model.status = ''
190
+ model.should_not be_valid
148
191
  end
149
192
  end
150
193
  end
@@ -15,7 +15,7 @@ ActiveRecord::Schema.define(:version => 0) do
15
15
  t.string "color"
16
16
  t.string "size"
17
17
  t.string "status"
18
- t.string "foo"
18
+ t.string "state"
19
19
  end
20
20
 
21
21
  create_table "schema_info", :id => false, :force => true do |t|
@@ -26,4 +26,4 @@ end
26
26
  I18n.load_path << 'spec/ru.yml'
27
27
  I18n.default_locale = :ru
28
28
 
29
- require File.dirname(__FILE__) + "/test_model" unless defined?(Model)
29
+ require File.dirname(__FILE__) + "/test_model" unless defined?(Model)
@@ -1,6 +1,12 @@
1
1
  class TestModel < ActiveRecord::Base
2
- has_enum :category, %w( stuff things misc )
3
- has_enum :color , %w( red green blue ) , :scopes => true
4
- has_enum :size , %w( small medium large ) , :query_methods => false
5
- has_enum :status , [:pending, :failed, :done]
6
- end
2
+
3
+ has_enum :category, %w[stuff things misc], :presence => {:on => :update}
4
+
5
+ has_enum :color, %w[red green blue], :scopes => true
6
+
7
+ has_enum :size, :query_methods => false
8
+
9
+ has_enum ["status", :state], [:pending, :failed, :done], :presence => true
10
+
11
+ end
12
+
metadata CHANGED
@@ -2,16 +2,17 @@
2
2
  name: has_enum
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.6.1
5
+ version: 0.7.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Andreas Korth
9
9
  - Konstantin Shabanov
10
+ - Dmitry Lihachev
10
11
  autorequire:
11
12
  bindir: bin
12
13
  cert_chain: []
13
14
 
14
- date: 2011-02-08 00:00:00 +06:00
15
+ date: 2011-03-24 00:00:00 +06:00
15
16
  default_executable:
16
17
  dependencies:
17
18
  - !ruby/object:Gem::Dependency
@@ -49,7 +50,7 @@ dependencies:
49
50
  version_requirements: *id003
50
51
  description: Gem for and Rails3 to easily handle enumeration attributes in ActiveRecord's models
51
52
  email:
52
- - kes.eclipse@gmail.com
53
+ - lda@openteam.ru
53
54
  executables: []
54
55
 
55
56
  extensions: []
@@ -67,7 +68,7 @@ files:
67
68
  - Rakefile
68
69
  - has_enum.gemspec
69
70
  - lib/has_enum.rb
70
- - lib/has_enum/active_record.rb
71
+ - lib/has_enum/class_methods.rb
71
72
  - lib/has_enum/helpers.rb
72
73
  - lib/has_enum/version.rb
73
74
  - spec/has_enum_spec.rb
@@ -99,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
100
  requirements: []
100
101
 
101
102
  rubyforge_project:
102
- rubygems_version: 1.5.0
103
+ rubygems_version: 1.6.1
103
104
  signing_key:
104
105
  specification_version: 3
105
106
  summary: Gem for Rails to easily handle enumeration attributes in models
@@ -1,86 +0,0 @@
1
- module HasEnum
2
- module ActiveRecord
3
- def self.included(base)
4
- base.write_inheritable_hash(:enum, {})
5
- base.extend(ClassMethods)
6
- end
7
-
8
- module ClassMethods
9
- def enum(attribute = nil)
10
- @enum ||= read_inheritable_attribute(:enum)
11
- attribute ? @enum[attribute.to_sym] : @enum
12
- end
13
-
14
- def has_enum(attribute, values, options = {})
15
- options.assert_valid_keys(:query_methods, :scopes)
16
- enum[attribute] = values.freeze
17
-
18
- values.each do |val|
19
- scope :"#{attribute}_#{val}", where(:"#{attribute}" => "#{val}")
20
- end if options[:scopes]
21
-
22
- values.each do |val|
23
- define_method(:"#{attribute}_#{val}?") { self.send(attribute) == val }
24
- end if options[:query_methods] != false
25
-
26
- define_method(:"#{attribute}=") do |value|
27
- if value.nil? or values.find{ |val| val == value }
28
- set_value = lambda { |value| value.is_a?(Symbol) ? Marshal.dump(value) : value }
29
- self[:"#{attribute}"] = value.nil? ? nil : set_value.call(value)
30
- else
31
- errors.add(:"#{attribute}", "#{value} is not in enum")
32
- end
33
- end
34
-
35
- define_method(:"#{attribute}") do
36
- load_value = lambda { |value| value[0].getbyte(0) == 4 ? Marshal.load(value) : value }
37
- value = self[:"#{attribute}"]
38
- value ? load_value.call(value) : nil
39
- end
40
-
41
- define_method "human_#{attribute}" do
42
- return nil unless self.send(attribute)
43
-
44
- defaults = ["activerecord.attributes.#{self.class.klass_key}.#{attribute}_enum.#{self.send(attribute)}"]
45
- defaults << self.send(attribute).to_s.humanize
46
- I18n.translate(defaults.shift, :default => defaults, :raise => true)
47
- end
48
- end
49
-
50
- def human_enum
51
- scope = "activerecord.attributes.#{self.klass_key}"
52
- i18n = lambda { |key| I18n.t(key, :scope => scope, :raise => true) }
53
- trans_hash = {}
54
-
55
- enum.each_pair do |attribute, values|
56
- begin
57
- trans_hash[attribute] = i18n.call("#{attribute}_enum")
58
- rescue I18n::MissingTranslationData
59
- hash = {}
60
- values.each do |value|
61
- hash[value.to_sym] = value.humanize
62
- end
63
- trans_hash[attribute] = hash
64
- end
65
- end
66
-
67
- trans_hash
68
- end
69
-
70
- def values_for_select_tag(enum)
71
- values = enum(enum)
72
- begin
73
- translation = I18n.translate("activerecord.attributes.#{self.klass_key}.#{enum}_enum", :raise => true)
74
-
75
- values.map { |value| [translation[value.to_sym], value] }
76
- rescue I18n::MissingTranslationData
77
- values.map { |value| [value.to_s.humanize, value] }
78
- end
79
- end
80
-
81
- def klass_key
82
- self.model_name.respond_to?(:i18n_key) ? self.model_name.i18n_key : self.name.underscore
83
- end
84
- end
85
- end
86
- end