enumbler 0.3.1 → 0.5.1

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: cd47cb4ab769babc898e8886bd974708ecf3f9e0a476b0a7c66218a27be01507
4
- data.tar.gz: e7933176d827c42b4bfe3aff27ec7e1eb7e382cffe00ba3bf0b808394d54a6d0
3
+ metadata.gz: da174585a626032f772439eea6f3a252ebaec0862f5fd47e151eac2db8403d15
4
+ data.tar.gz: 4e4d0f45f98074d5ee58df06ff57d825ee479fb3bdc3853b1e115f752474203c
5
5
  SHA512:
6
- metadata.gz: a2c1bab1e7db06447452132d15da099fd0e865602a9f35663ee86abdf242efb596ca156df2a105846072aefbe43cadf4351d7c0b9c9bee71453b1dd62c704e1a
7
- data.tar.gz: 03e911879677654e64eec1f7545c6de25faf44cab021b33f81d48335a9c2048410a6bd389d13b5f8ed643eef98d16f4432454cff5612b5417dbfba12143674f9
6
+ metadata.gz: a816d2d6b87a981167d55435031e1a1c5044385c76787d5fe9295e61c40041d5347f23c5710a6d6dcc2cbff78c543740438b30040818304e3b77ab68afca2cf5
7
+ data.tar.gz: 597f0bf8a11264b2543e8ed9f7142c952a9a5595510823328c630ebc75937b07e4a6a09f37dfba4bbab2e8fa4c7f5358a61f4ea26a1dc08f06ab2c92f451e2ac
@@ -1,27 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- enumbler (0.3.1)
5
- activerecord (~> 6.0.2)
6
- activesupport (~> 6.0.2)
4
+ enumbler (0.5.1)
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/
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.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'
@@ -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,20 +58,45 @@ 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
 
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
+
75
100
  # Return the record id for a given argument. Can accept an Integer, a
76
101
  # Symbol, or an instance of Enumbled model. This lookup is a database-free
77
102
  # lookup.
@@ -119,18 +144,28 @@ module Enumbler
119
144
  end
120
145
  end
121
146
 
122
- # Seeds the database with the Enumble data.
147
+ # Seeds the database with the Enumbler data.
123
148
  # @param delete_missing_records [Boolean] remove any records that are no
124
149
  # longer defined (default: false)
125
- 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)
126
152
  max_database_id = all.order('id desc').take&.id || 0
127
153
  max_enumble_id = enumbles.map(&:id).max
128
154
 
129
- 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
130
165
 
131
166
  discarded_ids = []
132
167
 
133
- (1..max_id).each do |id|
168
+ iterator.each do |id|
134
169
  enumble = @enumbles.find { |e| e.id == id }
135
170
 
136
171
  if enumble.nil?
@@ -140,7 +175,7 @@ module Enumbler
140
175
 
141
176
  record = find_or_initialize_by(id: id)
142
177
  record.attributes = enumble.attributes
143
- record.save!
178
+ record.save!(validate: validate)
144
179
  end
145
180
 
146
181
  where(id: discarded_ids).delete_all if delete_missing_records
@@ -148,22 +183,29 @@ module Enumbler
148
183
 
149
184
  # Seeds the database with the Enumble data, removing any records that are no
150
185
  # longer defined.
151
- def seed_the_enumbler!
152
- 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)
153
189
  end
154
190
 
155
191
  private
156
192
 
157
- def define_dynamic_methods_and_constants_for_enumbled_model(enum, id)
158
- method_name = "#{enum}?"
159
- not_method_name = "not_#{enum}?"
160
- 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}"
161
197
 
162
- const_set(enum.to_s.upcase, id)
163
- define_method(method_name) { self.id == id }
164
- 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 }
165
201
  alias_method alias_method_name, method_name
166
- 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
167
209
  end
168
210
  end
169
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.1'
4
+ VERSION = '0.5.1'
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.3.1
4
+ version: 0.5.1
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-26 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
@@ -144,6 +156,7 @@ files:
144
156
  - bin/setup
145
157
  - enumbler.gemspec
146
158
  - lib/enumbler.rb
159
+ - lib/enumbler/collection.rb
147
160
  - lib/enumbler/enabler.rb
148
161
  - lib/enumbler/enumble.rb
149
162
  - lib/enumbler/version.rb