enumbler 0.5.1 → 0.6.4

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: da174585a626032f772439eea6f3a252ebaec0862f5fd47e151eac2db8403d15
4
- data.tar.gz: 4e4d0f45f98074d5ee58df06ff57d825ee479fb3bdc3853b1e115f752474203c
3
+ metadata.gz: 96cdeb24ac43db515f90d6cf857e497281066c7dad0f5c2cf93d3aada54265dc
4
+ data.tar.gz: 13247cd26aa94ad10fe7f739fc8c3690d6e3702c59d77caceb80242a9df722d3
5
5
  SHA512:
6
- metadata.gz: a816d2d6b87a981167d55435031e1a1c5044385c76787d5fe9295e61c40041d5347f23c5710a6d6dcc2cbff78c543740438b30040818304e3b77ab68afca2cf5
7
- data.tar.gz: 597f0bf8a11264b2543e8ed9f7142c952a9a5595510823328c630ebc75937b07e4a6a09f37dfba4bbab2e8fa4c7f5358a61f4ea26a1dc08f06ab2c92f451e2ac
6
+ metadata.gz: 57cf510621d28df43bca6d5e7715e6bd9c1452a7a7457a1fd6ef3ae0f47425927deec6bc16e34dd2ccb94781cdb37f358d62825c8c64aed0294ffcd247fd6a93
7
+ data.tar.gz: 007d1e7c93fcf129ec0b13d037edd045e4e18b9d453a6dadc2381425a73f9b64424a83e4d976b0bd316a593ccf3c5e9d3f986c1cbb2bed35f7a9cb1f4dfc94b5
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- enumbler (0.5.1)
4
+ enumbler (0.6.4)
5
5
  activerecord (>= 5.2.3, < 6.1)
6
6
  activesupport (>= 5.2.3, < 6.1)
7
7
 
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`
@@ -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,18 @@ 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
+ raise_error_if_model_does_not_support_attributes(attributes)
62
+
63
+ id = validate_id_is_numeric(enum, id)
64
+
61
65
  @enumbles ||= Enumbler::Collection.new
62
66
  @enumbled_model = self
63
67
  @enumbler_label_column_name ||= :label
64
68
 
65
- enumble = Enumble.new(enum, id, label: label, label_column_name: @enumbler_label_column_name, **options)
69
+ enumble = Enumble.new(enum, id, label: label, label_column_name: @enumbler_label_column_name, **attributes)
66
70
 
67
71
  if @enumbles.include?(enumble)
68
72
  raise Error, "You cannot add the same Enumble twice! Attempted to add: #{enum}, #{id}."
@@ -97,6 +101,93 @@ module Enumbler
97
101
  @enumbler_label_column_name = label_column_name
98
102
  end
99
103
 
104
+ # See {.find_enumbles}. Simply returns the first object. Use when you
105
+ # want one argument to be found and not returned in an array.
106
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found and
107
+ # `raise_error: true`
108
+ # @param args [Integer, String, Symbol]
109
+ # @param case_sensitive [Boolean] should a String search be case sensitive
110
+ # (default: false)
111
+ # @param raise_error [Boolean] raise an error if not found (default:
112
+ # false)
113
+ # @return [Enumbler::Enumble]
114
+ def find_enumble(arg, case_sensitive: false, raise_error: false)
115
+ find_enumbles(arg, case_sensitive: case_sensitive, raise_error: raise_error).first
116
+ end
117
+
118
+ # See {.find_enumbles}. Simply returns the first object. Use when you
119
+ # want one argument to be found and not returned in an array. Raises error
120
+ # if none found.
121
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found and
122
+ # `raise_error: true`
123
+ # @param args [Integer, String, Symbol]
124
+ # @param case_sensitive [Boolean] should a String search be case sensitive
125
+ # (default: false)
126
+ # @return [Enumbler::Enumble]
127
+ def find_enumble!(arg, case_sensitive: false)
128
+ find_enumbles(arg, case_sensitive: case_sensitive, raise_error: true).first
129
+ end
130
+
131
+ # Finds an array of {Enumbler::Enumble} objects matching the given
132
+ # argument. Accepts an Integer, String, Symbol, or ActiveRecord instance.
133
+ #
134
+ # This method is designed to let you get information about the record
135
+ # without having to hit the database. Returns `nil` when none found
136
+ # unless `raise_error` is `true`.
137
+ #
138
+ # Color.find_enumbles(:black, 'white', 'not-found')
139
+ # #=> [Enumbler::Enumble<:black>, Enumbler::Enumble<:white>, nil]
140
+ #
141
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found and
142
+ # `raise_error: true`
143
+ # @param args [Integer, String, Symbol]
144
+ # @param case_sensitive [Boolean] should a String search be case sensitive
145
+ # (default: false)
146
+ # @param raise_error [Boolean] raise an error if not found (default:
147
+ # false)
148
+ # @return [Array<Enumbler::Enumble>]
149
+ def find_enumbles(*args, case_sensitive: false, raise_error: false)
150
+ args.flatten.compact.uniq.map do |arg|
151
+ err = "Unable to find a #{@enumbled_model}#enumble with #{arg}"
152
+
153
+ begin
154
+ arg = Integer(arg) # raises Type error if not a real integer
155
+ enumble = @enumbled_model.enumbles.find { |e| e.id == arg }
156
+ rescue TypeError, ArgumentError
157
+ enumble =
158
+ if arg.is_a?(Symbol)
159
+ @enumbled_model.enumbles.find { |e| e.enum == arg }
160
+ elsif arg.is_a?(String)
161
+ @enumbled_model.enumbles.find do |e|
162
+ case_sensitive ? e.label == arg : arg.casecmp?(e.label)
163
+ end
164
+ elsif arg.instance_of?(@enumbled_model)
165
+ arg.enumble
166
+ end
167
+ end
168
+
169
+ if enumble.present?
170
+ enumble
171
+ else
172
+ raise Error if raise_error
173
+
174
+ nil
175
+ end
176
+ rescue Error
177
+ raise Error, err
178
+ end
179
+ end
180
+
181
+ # See {.find_enumbles}. Same method, only raises error when none found.
182
+ # @raise [Error] when there is no [Enumbler::Enumble] to be found
183
+ # @param args [Integer, String, Symbol]
184
+ # @param case_sensitive [Boolean] should a String search be case sensitive
185
+ # (default: false)
186
+ # @return [Array<Enumbler::Enumble>]
187
+ def find_enumbles!(*args, case_sensitive: false)
188
+ find_enumbles(*args, case_sensitive: case_sensitive, raise_error: true)
189
+ end
190
+
100
191
  # Return the record id for a given argument. Can accept an Integer, a
101
192
  # Symbol, or an instance of Enumbled model. This lookup is a database-free
102
193
  # lookup.
@@ -107,9 +198,25 @@ module Enumbler
107
198
  #
108
199
  # @raise [Error] when there is no enumble to be found
109
200
  # @param arg [Integer, Symbol, Class]
201
+ # @param case_sensitive [Boolean] should a string search be performed with
202
+ # case sensitivity (default: false)
203
+ # @param raise_error [Boolean] raise an error if not found (default:
204
+ # false)
110
205
  # @return [Integer]
111
- def id_from_enumbler(arg)
112
- ids_from_enumbler(arg).first
206
+ def id_from_enumbler(arg, case_sensitive: false, raise_error: false)
207
+ ids_from_enumbler(arg, case_sensitive: case_sensitive, raise_error: raise_error).first
208
+ end
209
+
210
+ # See {.ids_from_enumbler}. Raises error if none found.
211
+ # @raise [Error] when there is no enumble to be found
212
+ # @param arg [Integer, Symbol, Class]
213
+ # @param case_sensitive [Boolean] should a string search be performed with
214
+ # case sensitivity (default: false)
215
+ # @param raise_error [Boolean] raise an error if not found (default:
216
+ # false)
217
+ # @return [Integer]
218
+ def id_from_enumbler!(arg, case_sensitive: false)
219
+ ids_from_enumbler(arg, case_sensitive: case_sensitive, raise_error: true).first
113
220
  end
114
221
 
115
222
  # Return the record id(s) based on different argument types. Can accept
@@ -118,30 +225,30 @@ module Enumbler
118
225
  #
119
226
  # Color.ids_from_enumbler(1, 2) # => [1, 2]
120
227
  # Color.ids_from_enumbler(:black, :white) # => [1, 2]
228
+ # Color.ids_from_enumbler('black', :white) # => [1, 2]
121
229
  # Color.ids_from_enumbler(Color.black, Color.white) # => [1, 2]
122
230
  #
123
231
  # @raise [Error] when there is no enumble to be found
124
232
  # @param *args [Integer, Symbol, Class]
233
+ # @param case_sensitive [Boolean] should a string search be performed with
234
+ # case sensitivity (default: false)
235
+ # @param raise_error [Boolean] raise an error if not found (default:
236
+ # false)
125
237
  # @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
238
+ def ids_from_enumbler(*args, case_sensitive: false, raise_error: false)
239
+ enumbles = find_enumbles(*args, case_sensitive: case_sensitive, raise_error: raise_error)
240
+ enumbles.map { |e| e&.id }
241
+ end
140
242
 
141
- enumble&.id || raise(Error, err)
142
- rescue Error
143
- raise Error, err
144
- end
243
+ # See {.ids_from_enumbler}. Raises error when none found.
244
+ # @raise [Error] when there is no enumble to be found
245
+ # @param *args [Integer, Symbol, Class]
246
+ # @param case_sensitive [Boolean] should a string search be performed with
247
+ # case sensitivity (default: false)
248
+ # @return [Array<Integer>]
249
+ def ids_from_enumbler!(*args, case_sensitive: false)
250
+ enumbles = find_enumbles!(*args, case_sensitive: case_sensitive)
251
+ enumbles.map(&:id)
145
252
  end
146
253
 
147
254
  # Seeds the database with the Enumbler data.
@@ -150,7 +257,7 @@ module Enumbler
150
257
  # @param validate [Boolean] validate on save?
151
258
  def seed_the_enumbler(delete_missing_records: false, validate: true)
152
259
  max_database_id = all.order('id desc').take&.id || 0
153
- max_enumble_id = enumbles.map(&:id).max
260
+ max_enumble_id = @enumbles.map(&:id).max
154
261
 
155
262
  # If we are not deleting records, we just need to update each listed
156
263
  # enumble and skip anything else in the database. If we are deleting
@@ -207,6 +314,29 @@ module Enumbler
207
314
  raise Enumbler::Error, "The attribute #{attr} is not supported on this Enumble."
208
315
  end
209
316
  end
317
+
318
+ # I accidentally forgot to provide an id one time and it was confusing as
319
+ # the last argument became the hash of options. This should help.
320
+ def validate_id_is_numeric(enum, id)
321
+ Integer(id)
322
+ rescue ArgumentError, TypeError
323
+ raise Enumbler::Error,
324
+ "You must provide a numeric primary key, like: `enumble :#{enum}, 1 `"
325
+ end
326
+
327
+ def raise_error_if_model_does_not_support_attributes(attributes)
328
+ return if attributes.blank?
329
+
330
+ unsupported_attrs = attributes.reject { |key, _value| has_attribute?(key) }
331
+
332
+ return if unsupported_attrs.blank?
333
+
334
+ raise Enumbler::Error,
335
+ "The model #{self} does not support the attribute(s): #{unsupported_attrs.keys.map(&:to_s).to_sentence}"
336
+ rescue ActiveRecord::StatementInvalid
337
+ warn "[Enumbler Warning] => Unable to find a table for #{self}."\
338
+ 'This is to be expected if there is a pending migration; however, if there is not then something is amiss.'
339
+ end
210
340
  end
211
341
  end
212
342
  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.1'
4
+ VERSION = '0.6.4'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enumbler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.4
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-26 00:00:00.000000000 Z
11
+ date: 2020-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord