enumerize 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ module Enumerize
2
+ module Scope
3
+ module Sequel
4
+ def enumerize(name, options={})
5
+ super
6
+
7
+ _enumerize_module.dependent_eval do
8
+ if defined?(::Sequel::Model) && self < ::Sequel::Model
9
+ if options[:scope]
10
+ _define_sequel_scope_methods!(name, options)
11
+ end
12
+
13
+ require 'enumerize/hooks/sequel_dataset'
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def _define_sequel_scope_methods!(name, options)
21
+ klass = self
22
+ scope_name = options[:scope] == true ? "with_#{name}" : options[:scope]
23
+
24
+ def_dataset_method scope_name do |*values|
25
+ values = values.map { |value| klass.enumerized_attributes[name].find_value(value).value }
26
+ values = values.first if values.size == 1
27
+
28
+ where(name => values)
29
+ end
30
+
31
+ if options[:scope] == true
32
+ def_dataset_method "without_#{name}" do |*values|
33
+ values = values.map { |value| klass.enumerized_attributes[name].find_value(value).value }
34
+ exclude(name => values)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,57 @@
1
+ module Enumerize
2
+ module SequelSupport
3
+ def enumerize(name, options={})
4
+ super
5
+
6
+ _enumerize_module.dependent_eval do
7
+ if defined?(::Sequel::Model) && self < ::Sequel::Model
8
+ include InstanceMethods
9
+
10
+ require 'enumerize/hooks/sequel_dataset'
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ module InstanceMethods
18
+ def validate
19
+ super
20
+
21
+ self.class.enumerized_attributes.each do |attr|
22
+ value = read_attribute_for_validation(attr.name)
23
+ next if value.blank?
24
+
25
+ if attr.kind_of? Multiple
26
+ errors.add attr.name, "is invalid" unless value.respond_to?(:all?) && value.all? { |v| v.blank? || attr.find_value(v) }
27
+ else
28
+ errors.add attr.name, "is not included in the list" unless attr.find_value(value)
29
+ end
30
+ end
31
+ end
32
+
33
+ def _set_default_value_for_enumerized_attributes
34
+ _enumerized_values_for_validation.delete_if do |k, v|
35
+ v.nil?
36
+ end
37
+
38
+ if defined?(Sequel::Plugins::Serialization::InstanceMethods)
39
+ modules = self.class.ancestors
40
+ plugin_idx = modules.index(Sequel::Plugins::Serialization::InstanceMethods)
41
+
42
+ if plugin_idx && plugin_idx < modules.index(Enumerize::SequelSupport::InstanceMethods)
43
+ abort "ERROR: You need to enable the Sequel serialization plugin before calling any enumerize methods on a model."
44
+ end
45
+
46
+ plugin_idx = modules.index(Sequel::Plugins::ValidationHelpers::InstanceMethods)
47
+
48
+ if plugin_idx && plugin_idx < modules.index(Enumerize::SequelSupport::InstanceMethods)
49
+ abort "ERROR: You need to enable the Sequel validation_helpers plugin before calling any enumerize methods on a model."
50
+ end
51
+ end
52
+
53
+ super
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/enumerize/set.rb CHANGED
@@ -37,12 +37,13 @@ module Enumerize
37
37
  end
38
38
 
39
39
  def texts
40
- @values.collect { |value| value.text }
40
+ @values.map(&:text)
41
41
  end
42
42
 
43
43
  delegate :join, to: :to_ary
44
44
 
45
45
  def ==(other)
46
+ return false unless other.respond_to?(:each)
46
47
  other.size == size && other.all? { |v| @values.include?(@attr.find_value(v)) }
47
48
  end
48
49
 
@@ -1,3 +1,3 @@
1
1
  module Enumerize
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,254 @@
1
+ require 'active_record'
2
+
3
+ silence_warnings do
4
+ ActiveRecord::Migration.verbose = false
5
+ ActiveRecord::Base.logger = Logger.new(nil)
6
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
7
+ end
8
+
9
+ ActiveRecord::Base.connection.instance_eval do
10
+ create_table :users do |t|
11
+ t.string :sex
12
+ t.string :role
13
+ t.string :account_type
14
+ end
15
+ end
16
+
17
+ class User < ActiveRecord::Base
18
+ extend Enumerize
19
+
20
+ enumerize :sex, :in => [:male, :female], scope: true
21
+ enumerize :role, :in => [:user, :admin], scope: :having_role
22
+ enumerize :account_type, :in => [:basic, :premium]
23
+ end
24
+
25
+ RSpec.describe Enumerize::Integrations::RSpec::Matcher do
26
+
27
+ let(:model) do
28
+ Class.new do
29
+ extend Enumerize
30
+
31
+ def self.name
32
+ 'Model'
33
+ end
34
+ end
35
+ end
36
+
37
+ subject do
38
+ model.new
39
+ end
40
+
41
+ describe 'without qualifier' do
42
+
43
+ it 'accepts when has defined a enumerize' do
44
+ model.enumerize(:sex, :in => [:male, :female])
45
+ expect(subject).to enumerize(:sex)
46
+ end
47
+
48
+ it 'rejects when has not defined a enumerize' do
49
+ message = 'Expected Model to define enumerize :sex'
50
+ expect do
51
+ expect(subject).to enumerize(:sex)
52
+ end.to fail_with(message)
53
+ end
54
+ end
55
+
56
+ describe '#in' do
57
+
58
+ context 'defined as array' do
59
+
60
+ before do
61
+ model.enumerize(:sex, :in => [:male, :female])
62
+ end
63
+
64
+ it 'accepts the right params as a array' do
65
+ expect(subject).to enumerize(:sex).in(:male, :female)
66
+ end
67
+
68
+ it 'accepts the the right params in a different order' do
69
+ expect(subject).to enumerize(:sex).in(:female, :male)
70
+ end
71
+
72
+ it 'rejects wrong params' do
73
+ message = 'Expected Model to define enumerize :sex in: "boy", "girl"'
74
+ expect do
75
+ expect(subject).to enumerize(:sex).in(:boy, :girl)
76
+ end.to fail_with(message)
77
+ end
78
+
79
+ it 'has the right message when negated' do
80
+ message = 'Did not expect Model to define enumerize :sex in: "female", "male"'
81
+ expect do
82
+ expect(subject).to_not enumerize(:sex).in(:male, :female)
83
+ end.to fail_with(message)
84
+ end
85
+ end
86
+
87
+ context 'defined as hash' do
88
+
89
+ before do
90
+ model.enumerize(:sex, :in => { male: 0, female: 1 })
91
+ end
92
+
93
+ it 'accepts the right params as a array' do
94
+ expect(subject).to enumerize(:sex).in(:male, :female)
95
+ end
96
+
97
+ it 'accepts the right params as a hash' do
98
+ expect(subject).to enumerize(:sex).in(male: 0, female: 1)
99
+ end
100
+
101
+ it 'accepts the right params as a hash in a different order' do
102
+ expect(subject).to enumerize(:sex).in(female: 1, male: 0)
103
+ end
104
+
105
+ it 'rejects wrong keys' do
106
+ message = 'Expected Model to define enumerize :sex in: "{:boy=>0, :girl=>1}"'
107
+ expect do
108
+ expect(subject).to enumerize(:sex).in(boy: 0, girl: 1)
109
+ end.to fail_with(message)
110
+ end
111
+
112
+ it 'rejects wrong values' do
113
+ message = 'Expected Model to define enumerize :sex in: "{:male=>2, :female=>3}"'
114
+ expect do
115
+ expect(subject).to enumerize(:sex).in(male: 2, female: 3)
116
+ end.to fail_with(message)
117
+ end
118
+ end
119
+
120
+ it 'has the right description' do
121
+ matcher = enumerize(:sex).in(:male, :female)
122
+ expect(matcher.description).to eq('define enumerize :sex in: "female", "male"')
123
+ end
124
+ end
125
+
126
+ describe '#with_default' do
127
+
128
+ before do
129
+ model.enumerize(:sex, :in => [:male, :female], default: :female)
130
+ end
131
+
132
+ it 'accepts the right default value' do
133
+ expect(subject).to enumerize(:sex).in(:male, :female).with_default(:female)
134
+ end
135
+
136
+ it 'rejects the wrong default value' do
137
+ message = 'Expected Model to define enumerize :sex in: "female", "male" with "male" as default value'
138
+ expect do
139
+ expect(subject).to enumerize(:sex).in(:male, :female).with_default(:male)
140
+ end.to fail_with(message)
141
+ end
142
+
143
+ it 'rejects if the `in` is wrong with a correct default value' do
144
+ message = 'Expected Model to define enumerize :sex in: "boy", "girl" with "female" as default value'
145
+ expect do
146
+ expect(subject).to enumerize(:sex).in(:boy, :girl).with_default(:female)
147
+ end.to fail_with(message)
148
+ end
149
+
150
+ it 'has the right description' do
151
+ matcher = enumerize(:sex).in(:male, :female).with_default(:female)
152
+ message = 'define enumerize :sex in: "female", "male" with "female" as default value'
153
+ expect(matcher.description).to eq(message)
154
+ end
155
+ end
156
+
157
+ describe '#with_i18n_scope' do
158
+
159
+ context 'defined as string' do
160
+
161
+ before do
162
+ model.enumerize(:sex, :in => [:male, :female], i18n_scope: 'sex')
163
+ end
164
+
165
+ it 'accepts the right i18n_scope' do
166
+ expect(subject).to enumerize(:sex).in(:male, :female).with_i18n_scope('sex')
167
+ end
168
+
169
+ it 'rejects the wrong i18n_scope' do
170
+ message = 'Expected Model to define enumerize :sex in: "female", "male" i18n_scope: "gender"'
171
+ expect do
172
+ expect(subject).to enumerize(:sex).in(:male, :female).with_i18n_scope('gender')
173
+ end.to fail_with(message)
174
+ end
175
+ end
176
+
177
+ context 'defined as array' do
178
+
179
+ before do
180
+ model.enumerize(:sex, :in => [:male, :female], i18n_scope: ['sex', 'more.sex'])
181
+ end
182
+
183
+ it 'accepts the wrong i18n_scope' do
184
+ expect(subject).to enumerize(:sex).in(:male, :female).with_i18n_scope(['sex', 'more.sex'])
185
+ end
186
+
187
+ it 'rejects the wrong i18n_scope' do
188
+ message = 'Expected Model to define enumerize :sex in: "female", "male" i18n_scope: ["sex"]'
189
+ expect do
190
+ expect(subject).to enumerize(:sex).in(:male, :female).with_i18n_scope(['sex'])
191
+ end.to fail_with(message)
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '#with_predicates' do
197
+
198
+ it 'accepts when predicates is defined as a boolean' do
199
+ model.enumerize(:sex, :in => [:male, :female], predicates: true)
200
+ expect(subject).to enumerize(:sex).in(:male, :female).with_predicates(true)
201
+ end
202
+
203
+ it 'accepts when predicates is defined as a hash' do
204
+ model.enumerize(:sex, :in => [:male, :female], predicates: { prefix: true })
205
+ expect(subject).to enumerize(:sex).in(:male, :female).with_predicates(prefix: true)
206
+ end
207
+
208
+ it 'rejects when predicates is not defined' do
209
+ model.enumerize(:sex, :in => [:male, :female])
210
+ message = 'Expected Model to define enumerize :sex in: "female", "male" predicates: true'
211
+ expect do
212
+ expect(subject).to enumerize(:sex).in(:male, :female).with_predicates(true)
213
+ end.to fail_with(message)
214
+ end
215
+ end
216
+
217
+ describe '#with_multiple' do
218
+
219
+ it 'accepts when has defined the multiple' do
220
+ model.enumerize(:sex, :in => [:male, :female], multiple: true)
221
+ expect(subject).to enumerize(:sex).in(:male, :female).with_multiple(true)
222
+ end
223
+
224
+ it 'rejects when has not defined the multiple' do
225
+ model.enumerize(:sex, :in => [:male, :female])
226
+ message = 'Expected Model to define enumerize :sex in: "female", "male" multiple: true'
227
+ expect do
228
+ expect(subject).to enumerize(:sex).in(:male, :female).with_multiple(true)
229
+ end.to fail_with(message)
230
+ end
231
+ end
232
+
233
+ describe '#with_scope' do
234
+
235
+ subject do
236
+ User.new
237
+ end
238
+
239
+ it 'accepts when scope is defined as a boolean' do
240
+ expect(subject).to enumerize(:sex).in(:male, :female).with_scope(true)
241
+ end
242
+
243
+ it 'accepts when scope is defined as a hash' do
244
+ expect(subject).to enumerize(:role).in(:user, :admin).with_scope(scope: :having_role)
245
+ end
246
+
247
+ it 'rejects when scope is not defined' do
248
+ message = 'Expected User to define enumerize :account_type in: "basic", "premium" scope: true'
249
+ expect do
250
+ expect(subject).to enumerize(:account_type).in(:basic, :premium).with_scope(true)
251
+ end.to fail_with(message)
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails'
2
+ require 'enumerize'
3
+ require 'rspec/matchers/fail_matchers'
4
+
5
+ RSpec.configure do |config|
6
+ config.disable_monkey_patching!
7
+ config.order = :random
8
+ Kernel.srand config.seed
9
+ config.filter_run focus: true
10
+ config.run_all_when_everything_filtered = true
11
+ config.expect_with :rspec do |expectations|
12
+ expectations.syntax = :expect
13
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
14
+ end
15
+ config.mock_with :rspec do |mocks|
16
+ mocks.syntax = :expect
17
+ mocks.verify_partial_doubles = true
18
+ end
19
+ if config.files_to_run.one?
20
+ config.default_formatter = 'doc'
21
+ end
22
+ config.warnings = true
23
+ config.include RSpec::Matchers::FailMatchers
24
+ end
@@ -0,0 +1,291 @@
1
+ require 'test_helper'
2
+ require 'sequel'
3
+ require 'logger'
4
+ require 'jdbc/sqlite3' if RUBY_PLATFORM == 'java'
5
+
6
+ module SequelTest
7
+ silence_warnings do
8
+ DB = if RUBY_PLATFORM == 'java'
9
+ Sequel.connect('jdbc:sqlite::memory:')
10
+ else
11
+ Sequel.sqlite
12
+ end
13
+ DB.loggers << Logger.new(nil)
14
+ end
15
+
16
+ DB.create_table :users do
17
+ primary_key :id
18
+ String :sex
19
+ String :role
20
+ String :lambda_role
21
+ String :name
22
+ String :interests
23
+ String :status
24
+ String :account_type, default: "basic"
25
+ end
26
+
27
+ DB.create_table :documents do
28
+ primary_key :id
29
+ String :visibility
30
+ Time :created_at
31
+ Time :updated_at
32
+ end
33
+
34
+ class Document < Sequel::Model
35
+ extend Enumerize
36
+ enumerize :visibility, :in => [:public, :private, :protected], :scope => true, :default => :public
37
+ end
38
+
39
+ module RoleEnum
40
+ extend Enumerize
41
+ enumerize :role, :in => [:user, :admin], :default => :user, scope: :having_role
42
+ enumerize :lambda_role, :in => [:user, :admin], :default => lambda { :admin }
43
+ end
44
+
45
+ class User < Sequel::Model
46
+ plugin :serialization, :json, :interests
47
+ plugin :dirty
48
+ plugin :defaults_setter
49
+ plugin :validation_helpers
50
+ extend Enumerize
51
+ include RoleEnum
52
+
53
+ enumerize :sex, :in => [:male, :female]
54
+
55
+ enumerize :interests, :in => [:music, :sports, :dancing, :programming], :multiple => true
56
+
57
+ enumerize :status, :in => { active: 1, blocked: 2 }, scope: true
58
+
59
+ enumerize :account_type, :in => [:basic, :premium]
60
+ end
61
+
62
+ class UniqStatusUser < User
63
+ def validate
64
+ super
65
+ validates_unique :status
66
+ validates_presence :sex
67
+ end
68
+ end
69
+
70
+ describe Enumerize::SequelSupport do
71
+ it 'sets nil if invalid value is passed' do
72
+ user = User.new
73
+ user.sex = :invalid
74
+ user.sex.must_equal nil
75
+ end
76
+
77
+ it 'saves value' do
78
+ User.filter.delete
79
+ user = User.new
80
+ user.sex = :female
81
+ user.save
82
+ user.sex.must_equal 'female'
83
+ end
84
+
85
+ it 'loads value' do
86
+ User.filter.delete
87
+ User.create(:sex => :male)
88
+ store_translations(:en, :enumerize => {:sex => {:male => 'Male'}}) do
89
+ user = User.first
90
+ user.sex.must_equal 'male'
91
+ user.sex_text.must_equal 'Male'
92
+ end
93
+ end
94
+
95
+ it 'has default value' do
96
+ User.new.role.must_equal 'user'
97
+ User.new.values[:role].must_equal 'user'
98
+ end
99
+
100
+ it 'does not set default value for not selected attributes' do
101
+ User.filter.delete
102
+ User.create(:sex => :male)
103
+
104
+ assert_equal [:id], User.select(:id).first.values.keys
105
+ end
106
+
107
+ it 'has default value with lambda' do
108
+ User.new.lambda_role.must_equal 'admin'
109
+ User.new.values[:lambda_role].must_equal 'admin'
110
+ end
111
+ it 'uses after_initialize callback to set default value' do
112
+ User.filter.delete
113
+ User.create(sex: 'male', lambda_role: nil)
114
+
115
+ user = User.where(:sex => 'male').first
116
+ user.lambda_role.must_equal 'admin'
117
+ end
118
+
119
+ it 'uses default value from db column' do
120
+ User.new.account_type.must_equal 'basic'
121
+ end
122
+
123
+ it 'validates inclusion' do
124
+ user = User.new
125
+ user.role = 'wrong'
126
+ user.wont_be :valid?
127
+ user.errors[:role].must_include 'is not included in the list'
128
+ end
129
+
130
+ it 'validates inclusion on mass assignment' do
131
+ assert_raises Sequel::ValidationFailed do
132
+ User.create(role: 'wrong')
133
+ end
134
+ end
135
+
136
+ it "uses persisted value for validation if it hasn't been set" do
137
+ user = User.create :sex => :male
138
+ User[user.id].read_attribute_for_validation(:sex).must_equal 'male'
139
+ end
140
+
141
+ it 'is valid with empty string assigned' do
142
+ user = User.new
143
+ user.role = ''
144
+ user.must_be :valid?
145
+ end
146
+
147
+ it 'stores nil when empty string assigned' do
148
+ user = User.new
149
+ user.role = ''
150
+ user.values[:role].must_equal nil
151
+ end
152
+
153
+ it 'supports multiple attributes' do
154
+ user = User.new
155
+ user.interests ||= []
156
+ user.interests.must_be_empty
157
+ user.interests << "music"
158
+ user.interests.must_equal %w(music)
159
+ user.save
160
+
161
+ user = User[user.id]
162
+ user.interests.must_be_instance_of Enumerize::Set
163
+ user.interests.must_equal %w(music)
164
+ user.interests << "sports"
165
+ user.interests.must_equal %w(music sports)
166
+
167
+ user.interests = []
168
+ interests = user.interests
169
+ interests << "music"
170
+ interests.must_equal %w(music)
171
+ interests << "dancing"
172
+ interests.must_equal %w(music dancing)
173
+ end
174
+
175
+ it 'returns invalid multiple value for validation' do
176
+ user = User.new
177
+ user.interests << :music
178
+ user.interests << :invalid
179
+ values = user.read_attribute_for_validation(:interests)
180
+ values.must_equal %w(music invalid)
181
+ end
182
+
183
+ it 'validates multiple attributes' do
184
+ user = User.new
185
+ user.interests << :invalid
186
+ user.wont_be :valid?
187
+
188
+ user.interests = Object.new
189
+ user.wont_be :valid?
190
+
191
+ user.interests = ['music', '']
192
+ user.must_be :valid?
193
+ end
194
+
195
+ it 'stores custom values for multiple attributes' do
196
+ User.filter.delete
197
+
198
+ klass = Class.new(User)
199
+ klass.enumerize :interests, in: { music: 0, sports: 1, dancing: 2, programming: 3}, multiple: true
200
+
201
+ user = klass.new
202
+ user.interests << :music
203
+ user.interests.must_equal %w(music)
204
+ user.save
205
+
206
+ user = klass[user.id]
207
+ user.interests.must_equal %w(music)
208
+ end
209
+
210
+ it 'adds scope' do
211
+ User.filter.delete
212
+
213
+ user_1 = User.create(status: :active, role: :admin)
214
+ user_2 = User.create(status: :blocked)
215
+
216
+ User.with_status(:active).to_a.must_equal [user_1]
217
+ User.with_status(:blocked).to_a.must_equal [user_2]
218
+ User.with_status(:active, :blocked).to_set.must_equal [user_1, user_2].to_set
219
+
220
+ User.without_status(:active).to_a.must_equal [user_2]
221
+ User.without_status(:active, :blocked).to_a.must_equal []
222
+
223
+ User.having_role(:admin).to_a.must_equal [user_1]
224
+ end
225
+
226
+ it 'allows either key or value as valid' do
227
+ user_1 = User.new(status: :active)
228
+ user_2 = User.new(status: 1)
229
+ user_3 = User.new(status: '1')
230
+
231
+ user_1.status.must_equal 'active'
232
+ user_2.status.must_equal 'active'
233
+ user_3.status.must_equal 'active'
234
+
235
+ user_1.must_be :valid?
236
+ user_2.must_be :valid?
237
+ user_3.must_be :valid?
238
+ end
239
+
240
+ it 'supports defining enumerized attributes on abstract class' do
241
+ Document.filter.delete
242
+
243
+ document = Document.new
244
+ document.visibility = :protected
245
+ document.visibility.must_equal 'protected'
246
+ end
247
+
248
+ it 'supports defining enumerized scopes on abstract class' do
249
+ Document.filter.delete
250
+
251
+ document_1 = Document.create(visibility: :public)
252
+ document_2 = Document.create(visibility: :private)
253
+
254
+ Document.with_visibility(:public).to_a.must_equal [document_1]
255
+ end
256
+
257
+ it 'validates uniqueness' do
258
+ user = User.create(status: :active, sex: "male")
259
+
260
+ user = UniqStatusUser.new
261
+ user.sex = "male"
262
+ user.status = :active
263
+ user.valid?.must_equal false
264
+
265
+ user.errors[:status].wont_be :empty?
266
+ end
267
+
268
+ it "doesn't update record" do
269
+ Document.filter.delete
270
+
271
+ expected = Time.new(2010, 10, 10)
272
+
273
+ document = Document.new
274
+ document.updated_at = expected
275
+ document.save
276
+
277
+ document = Document.last
278
+ document.save
279
+
280
+ assert_equal expected, document.updated_at
281
+ end
282
+
283
+ it 'changes from dirty should be serialized as scalar values' do
284
+ user = User.create(:status => :active)
285
+ user.status = :blocked
286
+
287
+ expected = { status: [1, 2] }.to_yaml
288
+ assert_equal expected, user.column_changes.to_yaml
289
+ end
290
+ end
291
+ end