enumbler 0.3.0 → 0.5.0

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: dc5b55b5cdb2b6514f035a98bdc6e5959066b4ea10dbfaae62001d1c33b04e30
4
- data.tar.gz: 807311615dc77faa62757ef3272ebb0b557962e7780af4151b841bc62db1158b
3
+ metadata.gz: 2eb7435b5b57b7fa3b04f7201bc41b3caaea6f148af6998fbdad02bdd44794bd
4
+ data.tar.gz: cf198237307efcbc457e55c3b046e612ba44a9fef52b86bb47f02a0989350df0
5
5
  SHA512:
6
- metadata.gz: 5a037f268d76bd8017788831aa300201571c6cf1246572599ae7972235f13df2b4984a9c8d4df007a7f546be34fcb645db955256a5f2255f792a560413e93f58
7
- data.tar.gz: c39c25eea9bff43c6e75df877ce33e8b33e616e1ec0ff01f10d361c3187c7b2711e4f85e81cdf06bfc38712ced4f6c0a2cd2c5047c688742ce3d5f42eac632b6
6
+ metadata.gz: e39538cefdfe366d8c944732796c0354c49f7fbc9930d7f3a4fb7912b6088188a1b1dbbd9f9395a4ea67f2c857a1ea7490f3631285daec68c388c19779b95602
7
+ data.tar.gz: f490870fc3d1bfe7f7a10bad501619668929f351f2882455e5e7da0581511957bd95fa08c77c09715e4b54f659212f742736256e9bca4706e906d1e0b3629551
@@ -1,27 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- enumbler (0.3.0)
5
- activerecord (>= 6.0)
6
- activesupport (>= 6.0)
4
+ enumbler (0.5.0)
5
+ activerecord (~> 6.0.2)
6
+ activesupport (~> 6.0.2)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activemodel (6.0.2.2)
12
- activesupport (= 6.0.2.2)
13
- activerecord (6.0.2.2)
14
- activemodel (= 6.0.2.2)
15
- activesupport (= 6.0.2.2)
16
- activesupport (6.0.2.2)
11
+ activemodel (6.0.3.1)
12
+ activesupport (= 6.0.3.1)
13
+ activerecord (6.0.3.1)
14
+ activemodel (= 6.0.3.1)
15
+ activesupport (= 6.0.3.1)
16
+ activesupport (6.0.3.1)
17
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
18
  i18n (>= 0.7, < 2)
19
19
  minitest (~> 5.1)
20
20
  tzinfo (~> 1.1)
21
- zeitwerk (~> 2.2)
21
+ zeitwerk (~> 2.2, >= 2.2.2)
22
22
  ast (2.4.0)
23
23
  concurrent-ruby (1.1.6)
24
- database_cleaner (1.8.4)
24
+ database_cleaner (1.8.5)
25
25
  database_cleaner-active_record (1.8.0)
26
26
  activerecord
27
27
  database_cleaner (~> 1.8.0)
@@ -32,9 +32,9 @@ GEM
32
32
  i18n (1.8.2)
33
33
  concurrent-ruby (~> 1.0)
34
34
  jaro_winkler (1.5.4)
35
- minitest (5.14.0)
35
+ minitest (5.14.1)
36
36
  parallel (1.19.1)
37
- parser (2.7.1.0)
37
+ parser (2.7.1.3)
38
38
  ast (~> 2.4.0)
39
39
  rainbow (3.0.0)
40
40
  rake (12.3.3)
@@ -43,15 +43,15 @@ GEM
43
43
  rspec-core (~> 3.9.0)
44
44
  rspec-expectations (~> 3.9.0)
45
45
  rspec-mocks (~> 3.9.0)
46
- rspec-core (3.9.1)
47
- rspec-support (~> 3.9.1)
48
- rspec-expectations (3.9.1)
46
+ rspec-core (3.9.2)
47
+ rspec-support (~> 3.9.3)
48
+ rspec-expectations (3.9.2)
49
49
  diff-lcs (>= 1.2.0, < 2.0)
50
50
  rspec-support (~> 3.9.0)
51
51
  rspec-mocks (3.9.1)
52
52
  diff-lcs (>= 1.2.0, < 2.0)
53
53
  rspec-support (~> 3.9.0)
54
- rspec-support (3.9.2)
54
+ rspec-support (3.9.3)
55
55
  rubocop (0.81.0)
56
56
  jaro_winkler (~> 1.5.1)
57
57
  parallel (~> 1.10)
data/README.md CHANGED
@@ -2,15 +2,30 @@
2
2
 
3
3
  `Enums` are terrific, but they lack integrity. Enumbler! The _enum enabler_! The goal is to allow one to maintain a true foreign_key database driven relationship that also behaves a little bit like an `enum`. Best of both worlds? We hope so.
4
4
 
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'enumbler'
11
+ ```
12
+
13
+ And then execute:
5
14
 
6
- ## Example
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install enumbler
20
+
21
+ ## Usage
7
22
 
8
23
  Suppose you have a `House` and you want to add some `colors` to the house. You are tempted to use an `enum` but the `Enumbler` is calling!
9
24
 
10
25
  ```ruby
11
26
  ActiveRecord::Schema.define do
12
27
  create_table :colors|t|
13
- t.string :label, null: false
28
+ t.string :label, null: false, index: { unique: true }
14
29
  end
15
30
 
16
31
  create_table :houses|t|
@@ -39,38 +54,47 @@ class House < ApplicationRecord
39
54
  end
40
55
 
41
56
  # This gives you some power:
42
- Color::BLACK # => 1
43
- Color.black # => equivalent to Color.find(1)
44
- Color.black.black? # => true
45
- Color.black.is_black # => true
46
- Color.white.not_black? # => true
57
+ Color::BLACK # => 1
58
+ Color.black # => equivalent to Color.find(1)
59
+ Color.black.black? # => true
60
+ Color.black.is_black # => true
61
+ Color.white.not_black? # => true
62
+
63
+ Color.black(:id) # => 1
64
+ Color.black(:enum) # => :black
65
+ Color.black(:label) # => 'black'
66
+ Color.black(:graphql_enum) # => 'BLACK'
47
67
 
48
68
  house = House.create!(color: Color.black)
49
69
  house.black?
50
70
  house.not_black?
51
71
 
52
- House.color(:black) # => [house]
72
+ house2 = House.create!(color: Color.white)
73
+ House.color(:black, :white) # => [house, house2]
53
74
  ```
54
75
 
55
- ## Installation
76
+ ### Use a column other than `label`
56
77
 
57
- Add this line to your application's Gemfile:
78
+ By default, the Enumbler expects a table in the database with a column `label`. However, you can change this to another underlying column name. Note that the enumbler still treats it as a `label` column; however it will be saved to the correct place in the database.
58
79
 
59
80
  ```ruby
60
- gem 'enumbler'
61
- ```
62
-
63
- And then execute:
64
-
65
- $ bundle install
66
-
67
- Or install it yourself as:
81
+ ActiveRecord::Schema.define do
82
+ create_table :feelings, force: true do |t|
83
+ t.string :emotion, null: false, index: { unique: true }
84
+ end
85
+ end
68
86
 
69
- $ gem install enumbler
87
+ class Feeling < ApplicationRecord
88
+ # @!parse extend Enumbler::Enabler::ClassMethods
89
+ include Enumbler::Enabler
70
90
 
71
- ## Usage
91
+ enumbler_label_column_name :emotion
72
92
 
73
- TODO: Write usage instructions here
93
+ enumble :sad, 1
94
+ enumble :happy, 2
95
+ enumble :verklempt, 3, label: 'overcome with emotion'
96
+ end
97
+ ```
74
98
 
75
99
  ## Development
76
100
 
@@ -80,7 +104,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
80
104
 
81
105
  ## Roadmap
82
106
 
83
- Ideally, we could make this work more like a traditional `enum`; for example, overriding the `.where` method by allowing something like: `House.where(color: :blue)` instead of `House.where_color(:blue)`. But right now am in a rush and not sure how to go about doing that properly.
107
+ * We need to add in support for additional attributes/columns in the enumbled table. For example, following the `Color` concept, we may want to have a column which is `hex` and stores the colors `hex` value (e.g., `FFFFFF`). This should be supported.
108
+ * Ideally, we could make this work more like a traditional `enum`; for example, overriding the `.where` method by allowing something like: `House.where(color: :blue)` instead of `House.where_color(:blue)`. But right now am in a rush and not sure how to go about doing that properly.
84
109
 
85
110
  ## Contributing
86
111
 
@@ -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'
33
- spec.add_dependency 'activesupport', '>= 6.0'
32
+ spec.add_dependency 'activerecord', '~> 6.0.2'
33
+ spec.add_dependency 'activesupport', '~> 6.0.2'
34
34
 
35
35
  spec.add_development_dependency 'database_cleaner-active_record', '~> 1.8.0'
36
36
  spec.add_development_dependency 'fuubar', '~> 2.5'
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'enumbler/collection'
3
4
  require 'enumbler/enumble'
4
5
  require 'enumbler/enabler'
5
6
  require 'enumbler/version'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumbler
4
+ # Not sure if this will be needed but was leaning toward a custom wrapper for
5
+ # our array holding the `enumbles` for our model. As it is, allows you to
6
+ # query them based on the enum:
7
+ #
8
+ # ```
9
+ # Color.enumbles.black # => [Enumbler::Enumble]
10
+ # ```
11
+ class Collection < Array
12
+ def method_missing(method_name, *args, &block)
13
+ enumble = find { |e| e.enum == method_name }
14
+ return enumble if enumble.present?
15
+
16
+ super
17
+ end
18
+
19
+ def respond_to_missing?(method_name, include_private = false)
20
+ enumble = find { |e| e.enum == method_name }
21
+ enumble.present? || super
22
+ end
23
+ end
24
+ end
@@ -34,7 +34,7 @@ module Enumbler
34
34
  #
35
35
  # # in your migration
36
36
  # create_table :colors, force: true do |t|
37
- # t.string :label, null: false
37
+ # t.string :label, null: false, index: { unique: true }
38
38
  # end
39
39
  #
40
40
  # class Color < ApplicationRecord
@@ -58,23 +58,63 @@ module Enumbler
58
58
  # @param **options [Hash] optional: additional attributes and values that
59
59
  # will be saved to the database for this enumble record
60
60
  def enumble(enum, id, label: nil, **options)
61
- @enumbles ||= []
61
+ @enumbles ||= Enumbler::Collection.new
62
62
  @enumbled_model = self
63
+ @enumbler_label_column_name ||= :label
63
64
 
64
- enumble = Enumble.new(enum, id, label: label, **options)
65
+ enumble = Enumble.new(enum, id, label: label, label_column_name: @enumbler_label_column_name, **options)
65
66
 
66
67
  if @enumbles.include?(enumble)
67
68
  raise Error, "You cannot add the same Enumble twice! Attempted to add: #{enum}, #{id}."
68
69
  end
69
70
 
70
- define_dynamic_methods_and_constants_for_enumbled_model(enum, id)
71
+ define_dynamic_methods_and_constants_for_enumbled_model(enumble)
71
72
 
72
73
  @enumbles << enumble
73
74
  end
74
75
 
75
- # Return the record id(s) based on different argument types. Can accept an
76
- # Integer, a Symbol, or an instance of Enumbled model. This lookup is a
77
- # databse-free lookup.
76
+ # By default, the Enumbler is expecting a table with an underlying column
77
+ # named `label` that represents the enum in the database. You can change
78
+ # this by calling `enumber_label_column_name` before you `enumble`!
79
+ #
80
+ # ActiveRecord::Schema.define do
81
+ # create_table :feelings, force: true do |t|
82
+ # t.string :emotion, null: false, index: { unique: true }
83
+ # end
84
+ # end
85
+ #
86
+ # class Feeling < ApplicationRecord
87
+ # # @!parse extend Enumbler::Enabler::ClassMethods
88
+ # include Enumbler::Enabler
89
+ #
90
+ # enumbler_label_column_name :emotion
91
+ #
92
+ # enumble :sad, 1
93
+ # enumble :happy, 2
94
+ # enumble :verklempt, 3, label: 'overcome with emotion'
95
+ # end
96
+ def enumbler_label_column_name(label_column_name)
97
+ @enumbler_label_column_name = label_column_name
98
+ end
99
+
100
+ # Return the record id for a given argument. Can accept an Integer, a
101
+ # Symbol, or an instance of Enumbled model. This lookup is a database-free
102
+ # lookup.
103
+ #
104
+ # Color.id_from_enumbler(1) # => 1
105
+ # Color.id_from_enumbler(:black) # => 1
106
+ # Color.id_from_enumbler(Color.black) # => 1
107
+ #
108
+ # @raise [Error] when there is no enumble to be found
109
+ # @param arg [Integer, Symbol, Class]
110
+ # @return [Integer]
111
+ def id_from_enumbler(arg)
112
+ ids_from_enumbler(arg).first
113
+ end
114
+
115
+ # Return the record id(s) based on different argument types. Can accept
116
+ # an Integer, a Symbol, or an instance of Enumbled model. This lookup is
117
+ # a database-free lookup.
78
118
  #
79
119
  # Color.ids_from_enumbler(1, 2) # => [1, 2]
80
120
  # Color.ids_from_enumbler(:black, :white) # => [1, 2]
@@ -104,18 +144,28 @@ module Enumbler
104
144
  end
105
145
  end
106
146
 
107
- # Seeds the database with the Enumble data.
147
+ # Seeds the database with the Enumbler data.
108
148
  # @param delete_missing_records [Boolean] remove any records that are no
109
149
  # longer defined (default: false)
110
- def seed_the_enumbler(delete_missing_records: false)
150
+ # @param validate [Boolean] validate on save?
151
+ def seed_the_enumbler(delete_missing_records: false, validate: true)
111
152
  max_database_id = all.order('id desc').take&.id || 0
112
153
  max_enumble_id = enumbles.map(&:id).max
113
154
 
114
- max_id = max_enumble_id > max_database_id ? max_enumble_id : max_database_id
155
+ # If we are not deleting records, we just need to update each listed
156
+ # enumble and skip anything else in the database. If we are deleting
157
+ # records, we need to know the max database id.
158
+ iterator = if !delete_missing_records
159
+ @enumbles.map(&:id)
160
+ elsif max_enumble_id > max_database_id
161
+ (1..max_enumble_id)
162
+ else
163
+ (1..max_database_id)
164
+ end
115
165
 
116
166
  discarded_ids = []
117
167
 
118
- (1..max_id).each do |id|
168
+ iterator.each do |id|
119
169
  enumble = @enumbles.find { |e| e.id == id }
120
170
 
121
171
  if enumble.nil?
@@ -125,7 +175,7 @@ module Enumbler
125
175
 
126
176
  record = find_or_initialize_by(id: id)
127
177
  record.attributes = enumble.attributes
128
- record.save!
178
+ record.save!(validate: validate)
129
179
  end
130
180
 
131
181
  where(id: discarded_ids).delete_all if delete_missing_records
@@ -133,22 +183,29 @@ module Enumbler
133
183
 
134
184
  # Seeds the database with the Enumble data, removing any records that are no
135
185
  # longer defined.
136
- def seed_the_enumbler!
137
- seed_the_enumbler(delete_missing_records: true)
186
+ # @param validate [Boolean] validate on save?
187
+ def seed_the_enumbler!(validate: true)
188
+ seed_the_enumbler(delete_missing_records: true, validate: validate)
138
189
  end
139
190
 
140
191
  private
141
192
 
142
- def define_dynamic_methods_and_constants_for_enumbled_model(enum, id)
143
- method_name = "#{enum}?"
144
- not_method_name = "not_#{enum}?"
145
- alias_method_name = "is_#{enum}"
193
+ def define_dynamic_methods_and_constants_for_enumbled_model(enumble)
194
+ method_name = "#{enumble.enum}?"
195
+ not_method_name = "not_#{enumble.enum}?"
196
+ alias_method_name = "is_#{enumble.enum}"
146
197
 
147
- const_set(enum.to_s.upcase, id)
148
- define_method(method_name) { self.id == id }
149
- define_method(not_method_name) { self.id != id }
198
+ const_set(enumble.enum.to_s.upcase, enumble.id)
199
+ define_method(method_name) { id == enumble.id }
200
+ define_method(not_method_name) { id != enumble.id }
150
201
  alias_method alias_method_name, method_name
151
- define_singleton_method(enum) { find(id) }
202
+ define_singleton_method(enumble.enum) do |attr = nil|
203
+ return find(enumble.id) if attr.nil?
204
+
205
+ enumble.send(attr)
206
+ rescue NoMethodError
207
+ raise Enumbler::Error, "The attribute #{attr} is not supported on this Enumble."
208
+ end
152
209
  end
153
210
  end
154
211
  end
@@ -2,13 +2,17 @@
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.
5
8
  class Enumble
6
- attr_reader :id, :enum, :label, :options
9
+ attr_reader :id, :enum, :label, :label_column_name, :options
7
10
 
8
- def initialize(enum, id, label: nil, **options)
11
+ def initialize(enum, id, label: nil, label_column_name: :label, **options)
9
12
  @id = id
10
13
  @enum = enum
11
14
  @label = label || enum.to_s.dasherize
15
+ @label_column_name = label_column_name
12
16
  @options = options
13
17
  end
14
18
 
@@ -20,7 +24,7 @@ module Enumbler
20
24
  def attributes
21
25
  {
22
26
  id: id,
23
- label: label,
27
+ label_column_name => label,
24
28
  }
25
29
  end
26
30
 
@@ -28,5 +32,16 @@ module Enumbler
28
32
  other.class == self.class &&
29
33
  (other.id == id || other.enum == enum || other.label == label)
30
34
  end
35
+
36
+ # Standardizing the enum for a GraphQL schema with an uppercase string
37
+ # value.
38
+ # @return [String]
39
+ def graphql_enum
40
+ enum.to_s.upcase
41
+ end
42
+
43
+ def to_s
44
+ enum
45
+ end
31
46
  end
32
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Enumbler
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enumbler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
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-04-29 00:00:00.000000000 Z
11
+ date: 2020-06-01 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
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
19
+ version: 6.0.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '6.0'
26
+ version: 6.0.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '6.0'
33
+ version: 6.0.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '6.0'
40
+ version: 6.0.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: database_cleaner-active_record
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -144,6 +144,7 @@ files:
144
144
  - bin/setup
145
145
  - enumbler.gemspec
146
146
  - lib/enumbler.rb
147
+ - lib/enumbler/collection.rb
147
148
  - lib/enumbler/enabler.rb
148
149
  - lib/enumbler/enumble.rb
149
150
  - lib/enumbler/version.rb