enumbler 0.5.0 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2eb7435b5b57b7fa3b04f7201bc41b3caaea6f148af6998fbdad02bdd44794bd
4
- data.tar.gz: cf198237307efcbc457e55c3b046e612ba44a9fef52b86bb47f02a0989350df0
3
+ metadata.gz: 29989e16025afc87b0db8fd70347b21978c683ab44c6ffa2d1f44a22c20fb166
4
+ data.tar.gz: 807a16dd6e6ff6e8c95b3f5337acf2f1ea456d07378dc702b8f2672c0b862276
5
5
  SHA512:
6
- metadata.gz: e39538cefdfe366d8c944732796c0354c49f7fbc9930d7f3a4fb7912b6088188a1b1dbbd9f9395a4ea67f2c857a1ea7490f3631285daec68c388c19779b95602
7
- data.tar.gz: f490870fc3d1bfe7f7a10bad501619668929f351f2882455e5e7da0581511957bd95fa08c77c09715e4b54f659212f742736256e9bca4706e906d1e0b3629551
6
+ metadata.gz: 2c8dbf495025d467ee30172289097169d84d23776320a1624d5e3920f2f66e738f606a15e61e2bbce696051c6d87ec99c811c45b3d9cdcb631f7e3bd1b961409
7
+ data.tar.gz: 3a6d12fe63ff37e1274b5ec3ec31023aa08f520f455b44695746a5555a20bc063283fbd6afaf2f9831d740588bcf090426647f052c1bc87e98d829a071a9fc7d
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- enumbler (0.5.0)
5
- activerecord (~> 6.0.2)
6
- activesupport (~> 6.0.2)
4
+ enumbler (0.6.3)
5
+ activerecord (>= 5.2.3, < 6.1)
6
+ activesupport (>= 5.2.3, < 6.1)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -26,6 +26,7 @@ Suppose you have a `House` and you want to add some `colors` to the house. You
26
26
  ActiveRecord::Schema.define do
27
27
  create_table :colors|t|
28
28
  t.string :label, null: false, index: { unique: true }
29
+ t.string :hex, null: true
29
30
  end
30
31
 
31
32
  create_table :houses|t|
@@ -42,8 +43,8 @@ end
42
43
  class Color < ApplicationRecord
43
44
  include Enumbler::Enabler
44
45
 
45
- enumble :black, 1
46
- enumble :white, 2
46
+ enumble :black, 1, hex: '000000'
47
+ enumble :white, 2, hex: 'ffffff'
47
48
  enumble :dark_brown, 3
48
49
  enumble :infinity, 4, label: 'Infinity - and beyond!'
49
50
  end
@@ -60,17 +61,43 @@ Color.black.black? # => true
60
61
  Color.black.is_black # => true
61
62
  Color.white.not_black? # => true
62
63
 
64
+ # Get attributes without hitting the database
63
65
  Color.black(:id) # => 1
64
66
  Color.black(:enum) # => :black
65
67
  Color.black(:label) # => 'black'
66
68
  Color.black(:graphql_enum) # => 'BLACK'
67
69
 
70
+ # Get an Enumble object from an id, label, enum, or ActiveRecord model
71
+ Color.find_enumbles(:black, 'white') # => [Enumbler::Enumble<:black>, Enumbler::Enumble<:white>]
72
+ Color.find_enumbles(:black, 'does-not-exist') # => [Enumbler::Enumble<:black>, nil]
73
+
74
+ Color.find_enumble(:black) # => Enumbler::Enumble<:black>
75
+
76
+ # raises errors if none found
77
+ Color.find_enumbles!!(:black, 'does-no-exist') # => raises Enumbler::Error
78
+ Color.find_enumble!(:does_not_exist) # => raises Enumbler::Error
79
+
80
+ # Get ids flexibly, without raising an error if none found
81
+ Color.ids_from_enumbler(:black, 'white') # => [1, 2]
82
+ Color.ids_from_enumbler(:black, 'does-no-exist') # => [1, nil]
83
+
84
+ # Raise an error if none found
85
+ Color.ids_from_enumbler!(:black, 'does-no-exist') # => raises Enumbler::Error
86
+ Color.id_from_enumbler!(:does_not_exist) # => raises Enumbler::Error
87
+
88
+ # Get enumble object by id
68
89
  house = House.create!(color: Color.black)
69
- house.black?
70
- house.not_black?
90
+
91
+ # These are all db-free lookups
92
+ house.color_label # => 'black'
93
+ house.color_enum # => :black
94
+ house.color_graphql_enum # => 'BLACK'
95
+ house.black? # => true
96
+ house.not_black? # => false
71
97
 
72
98
  house2 = House.create!(color: Color.white)
73
- House.color(:black, :white) # => [house, house2]
99
+ House.color(:black, :white) # => ActiveRecord::Relation<house, house2>
100
+ House.color(Color.black, :white) # => ActiveRecord::Relation<house, house2>
74
101
  ```
75
102
 
76
103
  ### Use a column other than `label`
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ['lib']
31
31
 
32
- spec.add_dependency 'activerecord', '~> 6.0.2'
33
- spec.add_dependency 'activesupport', '~> 6.0.2'
32
+ spec.add_dependency 'activerecord', ['>= 5.2.3', '< 6.1']
33
+ spec.add_dependency 'activesupport', ['>= 5.2.3', '< 6.1']
34
34
 
35
35
  spec.add_development_dependency 'database_cleaner-active_record', '~> 1.8.0'
36
36
  spec.add_development_dependency 'fuubar', '~> 2.5'
@@ -65,6 +65,7 @@ module Enumbler
65
65
 
66
66
  belongs_to(name, scope, **options)
67
67
 
68
+ define_helper_attributes(name)
68
69
  define_dynamic_methods_for_enumbled_to_models(enumbled_model, prefix: prefix, scope_prefix: scope_prefix)
69
70
  rescue NameError
70
71
  raise Error, "The model #{class_name} cannot be found. Uninitialized constant."
@@ -86,6 +87,10 @@ module Enumbler
86
87
  where(column_name => enumbled_model.ids_from_enumbler(args))
87
88
  end
88
89
 
90
+ define_singleton_method("#{cmethod}!") do |*args|
91
+ where(column_name => enumbled_model.ids_from_enumbler!(args))
92
+ end
93
+
89
94
  enumbled_model.enumbles.each do |enumble|
90
95
  method_name = prefix ? "#{model_name}_#{enumble.enum}?" : "#{enumble.enum}?"
91
96
  not_method_name = prefix ? "#{model_name}_not_#{enumble.enum}?" : "not_#{enumble.enum}?"
@@ -93,5 +98,16 @@ module Enumbler
93
98
  define_method(not_method_name) { self[column_name] != enumble.id }
94
99
  end
95
100
  end
101
+
102
+ # Add the attirbutes:
103
+ #
104
+ # house.color_label #=> 'black'
105
+ # house.color_enum #=> :black
106
+ # house.color_graphql_enum #=> 'BLACK'
107
+ def define_helper_attributes(name)
108
+ %i[label enum graphql_enum].each do |sym|
109
+ define_method("#{name}_#{sym}") { send(name).enumble.send(sym) }
110
+ end
111
+ end
96
112
  end
97
113
  end
@@ -40,8 +40,8 @@ module Enumbler
40
40
  # class Color < ApplicationRecord
41
41
  # include Enumbler::Enabler
42
42
  #
43
- # enumble :black, 1
44
- # enumble :white, 2
43
+ # enumble :black, 1, hex: '000000'
44
+ # enumble :white, 2, hex: 'ffffff'
45
45
  # enumble :dark_brown, 3, # label: 'dark-brown'
46
46
  # enumble :black_hole, 3, label: 'Oh my! It is a black hole!'
47
47
  # end
@@ -55,14 +55,16 @@ module Enumbler
55
55
  # @param enum [Symbol] the enum representation
56
56
  # @param id [Integer] the primary key value
57
57
  # @param label [String] optional: label for humans
58
- # @param **options [Hash] optional: additional attributes and values that
58
+ # @param **attributes [Hash] optional: additional attributes and values that
59
59
  # will be saved to the database for this enumble record
60
- def enumble(enum, id, label: nil, **options)
60
+ def enumble(enum, id, label: nil, **attributes)
61
61
  @enumbles ||= Enumbler::Collection.new
62
62
  @enumbled_model = self
63
63
  @enumbler_label_column_name ||= :label
64
64
 
65
- enumble = Enumble.new(enum, id, label: label, label_column_name: @enumbler_label_column_name, **options)
65
+ raise_error_if_model_does_not_support_attributes(attributes)
66
+
67
+ enumble = Enumble.new(enum, id, label: label, label_column_name: @enumbler_label_column_name, **attributes)
66
68
 
67
69
  if @enumbles.include?(enumble)
68
70
  raise Error, "You cannot add the same Enumble twice! Attempted to add: #{enum}, #{id}."
@@ -97,6 +99,93 @@ module Enumbler
97
99
  @enumbler_label_column_name = label_column_name
98
100
  end
99
101
 
102
+ # See {.find_enumbles}. Simply returns the first object. Use when you
103
+ # want one argument to be found and not returned in an array.
104
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found and
105
+ # `raise_error: true`
106
+ # @param args [Integer, String, Symbol]
107
+ # @param case_sensitive [Boolean] should a String search be case sensitive
108
+ # (default: false)
109
+ # @param raise_error [Boolean] raise an error if not found (default:
110
+ # false)
111
+ # @return [Enumbler::Enumble]
112
+ def find_enumble(arg, case_sensitive: false, raise_error: false)
113
+ find_enumbles(arg, case_sensitive: case_sensitive, raise_error: raise_error).first
114
+ end
115
+
116
+ # See {.find_enumbles}. Simply returns the first object. Use when you
117
+ # want one argument to be found and not returned in an array. Raises error
118
+ # if none found.
119
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found and
120
+ # `raise_error: true`
121
+ # @param args [Integer, String, Symbol]
122
+ # @param case_sensitive [Boolean] should a String search be case sensitive
123
+ # (default: false)
124
+ # @return [Enumbler::Enumble]
125
+ def find_enumble!(arg, case_sensitive: false)
126
+ find_enumbles(arg, case_sensitive: case_sensitive, raise_error: true).first
127
+ end
128
+
129
+ # Finds an array of {Enumbler::Enumble} objects matching the given
130
+ # argument. Accepts an Integer, String, Symbol, or ActiveRecord instance.
131
+ #
132
+ # This method is designed to let you get information about the record
133
+ # without having to hit the database. Returns `nil` when none found
134
+ # unless `raise_error` is `true`.
135
+ #
136
+ # Color.find_enumbles(:black, 'white', 'not-found')
137
+ # #=> [Enumbler::Enumble<:black>, Enumbler::Enumble<:white>, nil]
138
+ #
139
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found and
140
+ # `raise_error: true`
141
+ # @param args [Integer, String, Symbol]
142
+ # @param case_sensitive [Boolean] should a String search be case sensitive
143
+ # (default: false)
144
+ # @param raise_error [Boolean] raise an error if not found (default:
145
+ # false)
146
+ # @return [Array<Enumbler::Enumble>]
147
+ def find_enumbles(*args, case_sensitive: false, raise_error: false)
148
+ args.flatten.compact.uniq.map do |arg|
149
+ err = "Unable to find a #{@enumbled_model}#enumble with #{arg}"
150
+
151
+ begin
152
+ arg = Integer(arg) # raises Type error if not a real integer
153
+ enumble = @enumbled_model.enumbles.find { |e| e.id == arg }
154
+ rescue TypeError, ArgumentError
155
+ enumble =
156
+ if arg.is_a?(Symbol)
157
+ @enumbled_model.enumbles.find { |e| e.enum == arg }
158
+ elsif arg.is_a?(String)
159
+ @enumbled_model.enumbles.find do |e|
160
+ case_sensitive ? e.label == arg : arg.casecmp?(e.label)
161
+ end
162
+ elsif arg.instance_of?(@enumbled_model)
163
+ arg.enumble
164
+ end
165
+ end
166
+
167
+ if enumble.present?
168
+ enumble
169
+ else
170
+ raise Error if raise_error
171
+
172
+ nil
173
+ end
174
+ rescue Error
175
+ raise Error, err
176
+ end
177
+ end
178
+
179
+ # See {.find_enumbles}. Same method, only raises error when none found.
180
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found
181
+ # @param args [Integer, String, Symbol]
182
+ # @param case_sensitive [Boolean] should a String search be case sensitive
183
+ # (default: false)
184
+ # @return [Array<Enumbler::Enumble>]
185
+ def find_enumbles!(*args, case_sensitive: false)
186
+ find_enumbles(*args, case_sensitive: case_sensitive, raise_error: true)
187
+ end
188
+
100
189
  # Return the record id for a given argument. Can accept an Integer, a
101
190
  # Symbol, or an instance of Enumbled model. This lookup is a database-free
102
191
  # lookup.
@@ -107,9 +196,25 @@ module Enumbler
107
196
  #
108
197
  # @raise [Error] when there is no enumble to be found
109
198
  # @param arg [Integer, Symbol, Class]
199
+ # @param case_sensitive [Boolean] should a string search be performed with
200
+ # case sensitivity (default: false)
201
+ # @param raise_error [Boolean] raise an error if not found (default:
202
+ # false)
110
203
  # @return [Integer]
111
- def id_from_enumbler(arg)
112
- ids_from_enumbler(arg).first
204
+ def id_from_enumbler(arg, case_sensitive: false, raise_error: false)
205
+ ids_from_enumbler(arg, case_sensitive: case_sensitive, raise_error: raise_error).first
206
+ end
207
+
208
+ # See {.ids_from_enumbler}. Raises error if none found.
209
+ # @raise [Error] when there is no enumble to be found
210
+ # @param arg [Integer, Symbol, Class]
211
+ # @param case_sensitive [Boolean] should a string search be performed with
212
+ # case sensitivity (default: false)
213
+ # @param raise_error [Boolean] raise an error if not found (default:
214
+ # false)
215
+ # @return [Integer]
216
+ def id_from_enumbler!(arg, case_sensitive: false)
217
+ ids_from_enumbler(arg, case_sensitive: case_sensitive, raise_error: true).first
113
218
  end
114
219
 
115
220
  # Return the record id(s) based on different argument types. Can accept
@@ -118,30 +223,30 @@ module Enumbler
118
223
  #
119
224
  # Color.ids_from_enumbler(1, 2) # => [1, 2]
120
225
  # Color.ids_from_enumbler(:black, :white) # => [1, 2]
226
+ # Color.ids_from_enumbler('black', :white) # => [1, 2]
121
227
  # Color.ids_from_enumbler(Color.black, Color.white) # => [1, 2]
122
228
  #
123
229
  # @raise [Error] when there is no enumble to be found
124
230
  # @param *args [Integer, Symbol, Class]
231
+ # @param case_sensitive [Boolean] should a string search be performed with
232
+ # case sensitivity (default: false)
233
+ # @param raise_error [Boolean] raise an error if not found (default:
234
+ # false)
125
235
  # @return [Array<Integer>]
126
- def ids_from_enumbler(*args)
127
- args.flatten.compact.uniq.map do |arg|
128
- err = "Unable to find a #{@enumbled_model}#enumble with #{arg}"
129
-
130
- begin
131
- arg = Integer(arg) # raises Type error if not a real integer
132
- enumble = @enumbled_model.enumbles.find { |e| e.id == arg }
133
- rescue TypeError
134
- enumble = if arg.is_a?(Symbol)
135
- @enumbled_model.enumbles.find { |e| e.enum == arg }
136
- elsif arg.instance_of?(@enumbled_model)
137
- arg.enumble
138
- end
139
- end
236
+ def ids_from_enumbler(*args, case_sensitive: false, raise_error: false)
237
+ enumbles = find_enumbles(*args, case_sensitive: case_sensitive, raise_error: raise_error)
238
+ enumbles.map { |e| e&.id }
239
+ end
140
240
 
141
- enumble&.id || raise(Error, err)
142
- rescue Error
143
- raise Error, err
144
- end
241
+ # See {.ids_from_enumbler}. Raises error when none found.
242
+ # @raise [Error] when there is no enumble to be found
243
+ # @param *args [Integer, Symbol, Class]
244
+ # @param case_sensitive [Boolean] should a string search be performed with
245
+ # case sensitivity (default: false)
246
+ # @return [Array<Integer>]
247
+ def ids_from_enumbler!(*args, case_sensitive: false)
248
+ enumbles = find_enumbles!(*args, case_sensitive: case_sensitive)
249
+ enumbles.map(&:id)
145
250
  end
146
251
 
147
252
  # Seeds the database with the Enumbler data.
@@ -150,7 +255,7 @@ module Enumbler
150
255
  # @param validate [Boolean] validate on save?
151
256
  def seed_the_enumbler(delete_missing_records: false, validate: true)
152
257
  max_database_id = all.order('id desc').take&.id || 0
153
- max_enumble_id = enumbles.map(&:id).max
258
+ max_enumble_id = @enumbles.map(&:id).max
154
259
 
155
260
  # If we are not deleting records, we just need to update each listed
156
261
  # enumble and skip anything else in the database. If we are deleting
@@ -207,6 +312,20 @@ module Enumbler
207
312
  raise Enumbler::Error, "The attribute #{attr} is not supported on this Enumble."
208
313
  end
209
314
  end
315
+
316
+ def raise_error_if_model_does_not_support_attributes(attributes)
317
+ return if attributes.blank?
318
+
319
+ unsupported_attrs = attributes.reject { |key, _value| has_attribute?(key) }
320
+
321
+ return if unsupported_attrs.blank?
322
+
323
+ raise Enumbler::Error,
324
+ "The model #{self} does not support the attribute(s): #{unsupported_attrs.keys.map(&:to_s).to_sentence}"
325
+ rescue ActiveRecord::StatementInvalid
326
+ warn "[Enumbler Warning] => Unable to find a table for #{self}."\
327
+ 'This is to be expected if there is a pending migration; however, if there is not then something is amiss.'
328
+ end
210
329
  end
211
330
  end
212
331
  end
@@ -2,18 +2,15 @@
2
2
 
3
3
  module Enumbler
4
4
  # Class that holds each row of Enumble data.
5
- #
6
- # @todo We need to support additional options/attributes beyond the id/label
7
- # pairs. Is on the backburner for a moment.
8
5
  class Enumble
9
- attr_reader :id, :enum, :label, :label_column_name, :options
6
+ attr_reader :id, :enum, :label, :label_column_name
10
7
 
11
- def initialize(enum, id, label: nil, label_column_name: :label, **options)
8
+ def initialize(enum, id, label: nil, label_column_name: :label, **attributes)
12
9
  @id = id
13
10
  @enum = enum
14
11
  @label = label || enum.to_s.dasherize
15
12
  @label_column_name = label_column_name
16
- @options = options
13
+ @additional_attributes = attributes || {}
17
14
  end
18
15
 
19
16
  def ==(other)
@@ -22,10 +19,20 @@ module Enumbler
22
19
  end
23
20
 
24
21
  def attributes
25
- {
22
+ @additional_attributes.merge({
26
23
  id: id,
27
24
  label_column_name => label,
28
- }
25
+ })
26
+ end
27
+
28
+ # Used to return itself from a class method.
29
+ #
30
+ # ```
31
+ # Color.black(:enumble) #=> <Enumble:0x00007fb4396a78c8>
32
+ # ```
33
+ # @return [Enumbler::Enumble]
34
+ def enumble
35
+ self
29
36
  end
30
37
 
31
38
  def eql?(other)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Enumbler
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.3'
5
5
  end
metadata CHANGED
@@ -1,43 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enumbler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damon Timm
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-01 00:00:00.000000000 Z
11
+ date: 2020-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.3
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: 6.0.2
22
+ version: '6.1'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 5.2.3
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: 6.0.2
32
+ version: '6.1'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: activesupport
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - "~>"
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 5.2.3
40
+ - - "<"
32
41
  - !ruby/object:Gem::Version
33
- version: 6.0.2
42
+ version: '6.1'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
- - - "~>"
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 5.2.3
50
+ - - "<"
39
51
  - !ruby/object:Gem::Version
40
- version: 6.0.2
52
+ version: '6.1'
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: database_cleaner-active_record
43
55
  requirement: !ruby/object:Gem::Requirement