enumerize 0.8.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -2
  5. data/CHANGELOG.md +91 -0
  6. data/Gemfile +4 -22
  7. data/{Gemfile.rails4 → Gemfile.global} +2 -5
  8. data/Gemfile.mongo_mapper +5 -0
  9. data/Gemfile.rails40 +5 -0
  10. data/README.md +171 -8
  11. data/Rakefile +2 -4
  12. data/lib/enumerize/activerecord.rb +3 -29
  13. data/lib/enumerize/attribute.rb +41 -9
  14. data/lib/enumerize/base.rb +25 -10
  15. data/lib/enumerize/hooks/sequel_dataset.rb +19 -0
  16. data/lib/enumerize/integrations/rspec/matcher.rb +107 -26
  17. data/lib/enumerize/mongoid.rb +13 -0
  18. data/lib/enumerize/predicatable.rb +7 -16
  19. data/lib/enumerize/predicates.rb +3 -1
  20. data/lib/enumerize/scope/activerecord.rb +37 -0
  21. data/lib/enumerize/scope/mongoid.rb +35 -0
  22. data/lib/enumerize/scope/sequel.rb +40 -0
  23. data/lib/enumerize/sequel.rb +57 -0
  24. data/lib/enumerize/set.rb +18 -8
  25. data/lib/enumerize/value.rb +8 -7
  26. data/lib/enumerize/version.rb +1 -1
  27. data/lib/enumerize.rb +23 -1
  28. data/spec/enumerize/integrations/rspec/matcher_spec.rb +258 -0
  29. data/spec/spec_helper.rb +28 -0
  30. data/test/activerecord_test.rb +58 -7
  31. data/test/attribute_test.rb +22 -0
  32. data/test/formtastic_test.rb +3 -12
  33. data/test/mongo_mapper_test.rb +6 -0
  34. data/test/mongoid_test.rb +55 -6
  35. data/test/multiple_test.rb +21 -0
  36. data/test/predicates_test.rb +6 -0
  37. data/test/sequel_test.rb +291 -0
  38. data/test/set_test.rb +14 -0
  39. data/test/simple_form_test.rb +0 -1
  40. data/test/support/view_test_helper.rb +4 -0
  41. data/test/test_helper.rb +23 -2
  42. data/test/value_test.rb +51 -21
  43. metadata +37 -8
  44. data/lib/enumerize/form_helper.rb +0 -23
  45. data/test/rspec_matcher_test.rb +0 -76
  46. data/test/rspec_spec.rb +0 -13
@@ -0,0 +1,19 @@
1
+ module Enumerize
2
+ module Hooks
3
+ module SequelDataset
4
+ def self.included(klass)
5
+ klass.alias_method_chain :literal_append, :enumerize
6
+ end
7
+
8
+ def literal_append_with_enumerize(sql, v)
9
+ if v.is_a?(Enumerize::Value)
10
+ literal_append(sql, v.value)
11
+ else
12
+ literal_append_without_enumerize(sql, v)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ::Sequel::Dataset.send :include, Enumerize::Hooks::SequelDataset
@@ -2,37 +2,57 @@ module Enumerize
2
2
  module Integrations
3
3
  module RSpec
4
4
  class Matcher
5
- attr_accessor :attr, :values, :subject, :default
6
5
 
7
- def initialize(attr)
8
- self.attr = attr
6
+ def initialize(expected_attr)
7
+ self.expected_attr = expected_attr
9
8
  end
10
9
 
11
- def in(*values)
12
- self.values = values.map(&:to_s).sort
10
+ def in(*expected_values)
11
+ self.expected_values = expected_values.flatten
13
12
  self
14
13
  end
15
14
 
16
- def with_default(default)
17
- self.default = default.to_s
15
+ def with_default(expected_default)
16
+ self.expected_default = expected_default.to_s
18
17
  self
19
18
  end
20
19
 
21
- def failure_message
22
- message = " expected :#{attr} to allow value#{values.size == 1 ? nil : 's'}: #{quote_values(values)},"
23
- message += " but it allows #{quote_values(enumerized_values)} instead"
20
+ def with_i18n_scope(expected_i18n_scope)
21
+ self.expected_i18n_scope = expected_i18n_scope
22
+ self
23
+ end
24
24
 
25
- if default && !matches_default_value?
26
- message = " expected :#{attr} to have #{default.inspect} as default value,"
27
- message += " but it sets #{enumerized_default.inspect} instead"
28
- end
25
+ def with_predicates(expected_predicates)
26
+ self.expected_predicates = expected_predicates
27
+ self
28
+ end
29
+
30
+ def with_multiple(expected_multiple)
31
+ self.expected_multiple = expected_multiple
32
+ self
33
+ end
34
+
35
+ def with_scope(expected_scope)
36
+ self.expected_scope = expected_scope
37
+ self
38
+ end
39
+
40
+ def failure_message
41
+ "Expected #{expectation}"
42
+ end
29
43
 
30
- message
44
+ def failure_message_when_negated
45
+ "Did not expect #{expectation}"
31
46
  end
32
47
 
33
48
  def description
34
- description = "enumerize :#{attr} in: #{quote_values(values)}"
35
- description += " with #{default.inspect} as default value" if default
49
+ description = "define enumerize :#{expected_attr}"
50
+ description += " in: #{quote_values(expected_values)}" if expected_values
51
+ description += " with #{expected_default.inspect} as default value" if expected_default
52
+ description += " i18n_scope: #{expected_i18n_scope.inspect}" if expected_i18n_scope
53
+ description += " predicates: #{expected_predicates.inspect}" if expected_predicates
54
+ description += " multiple: #{expected_multiple.inspect}" if expected_multiple
55
+ description += " scope: #{expected_scope.inspect}" if expected_scope
36
56
 
37
57
  description
38
58
  end
@@ -41,36 +61,97 @@ module Enumerize
41
61
  self.subject = subject
42
62
  matches = true
43
63
 
44
- matches &= matches_attributes?
45
- matches &= matches_default_value? if default
64
+ matches &= matches_attribute?
65
+ matches &= matches_values? if expected_values
66
+ matches &= matches_default_value? if expected_default
67
+ matches &= matches_i18n_scope? if expected_i18n_scope
68
+ matches &= matches_predicates? if expected_predicates
69
+ matches &= matches_multiple? if expected_multiple
70
+ matches &= matches_scope? if expected_scope
46
71
 
47
72
  matches
48
73
  end
49
74
 
50
75
  private
76
+ attr_accessor :expected_attr, :expected_values, :subject, :expected_default,
77
+ :expected_i18n_scope, :expected_predicates, :expected_multiple,
78
+ :expected_scope
51
79
 
52
- def matches_attributes?
53
- values == enumerized_values
80
+ def expectation
81
+ "#{subject.class.name} to #{description}"
82
+ end
83
+
84
+ def matches_attribute?
85
+ attributes.present?
86
+ end
87
+
88
+ def matches_values?
89
+ matches_array_values? || matches_hash_values?
90
+ end
91
+
92
+ def matches_array_values?
93
+ sorted_values == enumerized_values
94
+ end
95
+
96
+ def matches_hash_values?
97
+ return unless expected_values.first.is_a?(Hash)
98
+ expected_values.first.all? { |k, v| enumerized_value_hash[k.to_s] == v; }
54
99
  end
55
100
 
56
101
  def matches_default_value?
57
- default == enumerized_default
102
+ expected_default == enumerized_default
103
+ end
104
+
105
+ def matches_i18n_scope?
106
+ attributes.i18n_scope == expected_i18n_scope
107
+ end
108
+
109
+ def matches_predicates?
110
+ if expected_predicates.is_a?(TrueClass)
111
+ subject.respond_to?("#{sorted_values.first}?")
112
+ else
113
+ subject.respond_to?("#{expected_attr}_#{attributes.values.first}?")
114
+ end
115
+ end
116
+
117
+ def matches_multiple?
118
+ subject.instance_variable_defined?("@_#{expected_attr}_enumerized_set")
119
+ end
120
+
121
+ def matches_scope?
122
+ if expected_scope.is_a?(TrueClass)
123
+ subject_class.respond_to?("with_#{expected_attr}")
124
+ else
125
+ subject_class.respond_to?(expected_scope[:scope])
126
+ end
127
+ end
128
+
129
+ def sorted_values
130
+ @sorted_values ||=expected_values.map(&:to_s).sort
58
131
  end
59
132
 
60
133
  def enumerized_values
61
- @enumerized_values ||= attributes[attr.to_s].values.sort
134
+ @enumerized_values ||= attributes.values.sort
62
135
  end
63
136
 
64
137
  def enumerized_default
65
- @enumerized_default ||= attributes[attr.to_s].default_value
138
+ @enumerized_default ||= attributes.default_value
139
+ end
140
+
141
+ def enumerized_value_hash
142
+ @enumerized_value_hash ||= attributes.instance_variable_get('@value_hash')
66
143
  end
67
144
 
68
145
  def attributes
69
- subject.class.enumerized_attributes.attributes
146
+ subject_class.enumerized_attributes.attributes[expected_attr.to_s]
147
+ end
148
+
149
+ def subject_class
150
+ @subject_class ||= subject.class
70
151
  end
71
152
 
72
153
  def quote_values(values)
73
- values.map(&:inspect).join(', ')
154
+ sorted_values.map(&:inspect).join(', ')
74
155
  end
75
156
  end
76
157
  end
@@ -0,0 +1,13 @@
1
+ module Enumerize
2
+ module MongoidSupport
3
+ def enumerize(name, options={})
4
+ super
5
+
6
+ _enumerize_module.dependent_eval do
7
+ if self < ::Mongoid::Document
8
+ after_initialize :_set_default_value_for_enumerized_attributes
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,29 +1,20 @@
1
1
  module Enumerize
2
2
  module Predicatable
3
- def method_missing(method, *args, &block)
4
- if boolean_method?(method)
5
- define_query_methods
6
- send(method, *args, &block)
7
- else
8
- super
9
- end
10
- end
11
-
12
3
  def respond_to_missing?(method, include_private=false)
13
- boolean_method?(method)
4
+ predicate_method?(method) || super
14
5
  end
15
6
 
16
7
  private
17
8
 
18
- def define_query_methods
19
- @attr.values.each do |value|
20
- unless singleton_methods.include?(:"#{value}?")
21
- define_query_method(value)
22
- end
9
+ def method_missing(method, *args, &block)
10
+ if predicate_method?(method)
11
+ predicate_call(method[0..-2], *args, &block)
12
+ else
13
+ super
23
14
  end
24
15
  end
25
16
 
26
- def boolean_method?(method)
17
+ def predicate_method?(method)
27
18
  method[-1] == '?' && @attr.values.include?(method[0..-2])
28
19
  end
29
20
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
1
3
  module Enumerize
2
4
  # Predicate methods.
3
5
  #
@@ -61,7 +63,7 @@ module Enumerize
61
63
  end
62
64
 
63
65
  def names
64
- values.map { |v| "#{v}?" }
66
+ values.map { |v| "#{v.tr('-', '_')}?" }
65
67
  end
66
68
 
67
69
  def build(klass)
@@ -0,0 +1,37 @@
1
+ module Enumerize
2
+ module Scope
3
+ module ActiveRecord
4
+ def enumerize(name, options={})
5
+ super
6
+
7
+ _enumerize_module.dependent_eval do
8
+ if self < ::ActiveRecord::Base
9
+ if options[:scope]
10
+ _define_activerecord_scope_methods!(name, options)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def _define_activerecord_scope_methods!(name, options)
19
+ scope_name = options[:scope] == true ? "with_#{name}" : options[:scope]
20
+
21
+ define_singleton_method scope_name do |*values|
22
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
23
+ values = values.first if values.size == 1
24
+
25
+ where(name => values)
26
+ end
27
+
28
+ if options[:scope] == true
29
+ define_singleton_method "without_#{name}" do |*values|
30
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
31
+ where(arel_table[name].not_in(values))
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ module Enumerize
2
+ module Scope
3
+ module Mongoid
4
+ def enumerize(name, options={})
5
+ super
6
+
7
+ _enumerize_module.dependent_eval do
8
+ if self < ::Mongoid::Document
9
+ if options[:scope]
10
+ _define_mongoid_scope_methods!(name, options)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def _define_mongoid_scope_methods!(name, options)
19
+ scope_name = options[:scope] == true ? "with_#{name}" : options[:scope]
20
+
21
+ define_singleton_method scope_name do |*values|
22
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
23
+ self.in(name => values)
24
+ end
25
+
26
+ if options[:scope] == true
27
+ define_singleton_method "without_#{name}" do |*values|
28
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
29
+ not_in(name => values)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
1
3
  module Enumerize
2
4
  class Set
3
5
  include Enumerable
@@ -8,12 +10,15 @@ module Enumerize
8
10
  def initialize(obj, attr, values)
9
11
  @obj = obj
10
12
  @attr = attr
11
- @values = ::Set.new
13
+ @values = []
12
14
 
13
15
  if values.respond_to?(:each)
14
16
  values.each do |input|
15
17
  value = @attr.find_value(input)
16
- @values << value if value
18
+
19
+ if value && !@values.include?(value)
20
+ @values << value
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -31,9 +36,14 @@ module Enumerize
31
36
  @values.to_a
32
37
  end
33
38
 
39
+ def texts
40
+ @values.map(&:text)
41
+ end
42
+
34
43
  delegate :join, to: :to_ary
35
44
 
36
45
  def ==(other)
46
+ return false unless other.respond_to?(:each)
37
47
  other.size == size && other.all? { |v| @values.include?(@attr.find_value(v)) }
38
48
  end
39
49
 
@@ -52,14 +62,14 @@ module Enumerize
52
62
  "#<Enumerize::Set {#{join(', ')}}>"
53
63
  end
54
64
 
65
+ def encode_with(coder)
66
+ coder.represent_object(Array, @values)
67
+ end
68
+
55
69
  private
56
70
 
57
- def define_query_method(value)
58
- singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
59
- def #{value}?
60
- include?("#{value}")
61
- end
62
- RUBY
71
+ def predicate_call(value)
72
+ include?(value)
63
73
  end
64
74
 
65
75
  def mutate!
@@ -17,25 +17,26 @@ module Enumerize
17
17
  I18n.t(i18n_keys[0], :default => i18n_keys[1..-1])
18
18
  end
19
19
 
20
+ def ==(other)
21
+ super(other.to_s) || value == other
22
+ end
23
+
20
24
  def encode_with(coder)
21
25
  coder.represent_object(self.class.superclass, @value)
22
26
  end
23
27
 
24
28
  private
25
29
 
26
- def define_query_method(value)
27
- singleton_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
28
- def #{value}?
29
- #{value == self}
30
- end
31
- RUBY
30
+ def predicate_call(value)
31
+ value == self
32
32
  end
33
33
 
34
34
  def i18n_keys
35
35
  @i18n_keys ||= begin
36
36
  i18n_keys = i18n_scopes
37
+ i18n_keys << [:"enumerize.defaults.#{@attr.name}.#{self}"]
37
38
  i18n_keys << [:"enumerize.#{@attr.name}.#{self}"]
38
- i18n_keys << self.humanize # humanize value if there are no translations
39
+ i18n_keys << self.underscore.humanize # humanize value if there are no translations
39
40
  i18n_keys.flatten
40
41
  end
41
42
  end
@@ -1,3 +1,3 @@
1
1
  module Enumerize
2
- VERSION = "0.8.0"
2
+ VERSION = "1.1.1"
3
3
  end
data/lib/enumerize.rb CHANGED
@@ -13,6 +13,14 @@ module Enumerize
13
13
  autoload :ModuleAttributes, 'enumerize/module_attributes'
14
14
 
15
15
  autoload :ActiveRecordSupport, 'enumerize/activerecord'
16
+ autoload :SequelSupport, 'enumerize/sequel'
17
+ autoload :MongoidSupport, 'enumerize/mongoid'
18
+
19
+ module Scope
20
+ autoload :ActiveRecord, 'enumerize/scope/activerecord'
21
+ autoload :Sequel, 'enumerize/scope/sequel'
22
+ autoload :Mongoid, 'enumerize/scope/mongoid'
23
+ end
16
24
 
17
25
  def self.included(base)
18
26
  ActiveSupport::Deprecation.warn '`include Enumerize` was deprecated. Please use `extend Enumerize`.', caller
@@ -22,7 +30,21 @@ module Enumerize
22
30
  def self.extended(base)
23
31
  base.send :include, Enumerize::Base
24
32
  base.extend Enumerize::Predicates
25
- base.extend Enumerize::ActiveRecordSupport
33
+
34
+ if defined?(::ActiveRecord::Base)
35
+ base.extend Enumerize::ActiveRecordSupport
36
+ base.extend Enumerize::Scope::ActiveRecord
37
+ end
38
+
39
+ if defined?(::Mongoid::Document)
40
+ base.extend Enumerize::MongoidSupport
41
+ base.extend Enumerize::Scope::Mongoid
42
+ end
43
+
44
+ if defined?(::Sequel::Model)
45
+ base.extend Enumerize::SequelSupport
46
+ base.extend Enumerize::Scope::Sequel
47
+ end
26
48
 
27
49
  if defined?(::RailsAdmin)
28
50
  require 'enumerize/integrations/rails_admin'