swifter_enum 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f70feef4d3937618d057d99d62e1f08cc3a733580c523f9e131508d2386d1a1e
4
- data.tar.gz: d47f2661ab4bf284eb13b7c40c358a226c6a6c5fc416567498fa312a4abc7960
3
+ metadata.gz: 256304e01311b60f1cf26bc7ad5d52fb7ba625bbc6c3b4216e6af129042d3516
4
+ data.tar.gz: fd82b318f1aa43a43c87eeebb965a36515b40f23f1a4a92928ed7d2e374052d5
5
5
  SHA512:
6
- metadata.gz: c522bc3565a9e6d98da82f8e4e61b59e1e00632348600ab0d1550dd56760e4b1bc76a9e2e3c1a5a4de8f93131f81e051e239aa0d74327f46c65c99fe0f4eb40b
7
- data.tar.gz: 2d3e496779b2c8ad21d393286279de27353c7c04f71f749ab8f15c371e270f936c08e5e108d79c564798eba640f2f5bf2bfedfd6076ee1d88acc3ba7b6b9187f
6
+ metadata.gz: 3e22b72fbbdc5a9090bc18189ea10542c08ad17fc4236067c304274c8bd5100090adf7e2eb5f94b264f5f9f1fe8b84a64c9e9bff849a24249b60d689ab53b443
7
+ data.tar.gz: 67cfb099c8c99eb3d754802841f49a144f6fcfba54cd2855dd6b3f645b0f16946870fe9f0bc1509f5ba06cedffbb92460755bf35a629156b10414a0fd3be6a21
data/CLAUDE.md ADDED
@@ -0,0 +1,46 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ### Testing
8
+ - Run all tests: `bundle exec appraisal rake test`
9
+ - Run specific test file: `bundle exec appraisal rake test TEST=test/path/to/test_file.rb`
10
+ - Test with specific Rails version: `bundle exec appraisal rails-7.1 rake test`
11
+
12
+ ### Code Quality
13
+ - Run Standard Ruby linter: `standardrb`
14
+ - Auto-fix linting issues: `standardrb --fix`
15
+
16
+ ### Building & Publishing
17
+ - Build gem: `rake build`
18
+ - Release new version: `rake release:publish` (builds, tags, and publishes to RubyGems)
19
+
20
+ ## Architecture
21
+
22
+ SwifterEnum is a Ruby gem that extends Rails ActiveRecord enums by using classes instead of simple string/integer values. This allows encapsulating enum-related logic within the enum class itself.
23
+
24
+ ### Core Components
25
+
26
+ 1. **SwifterEnum::Base** (`lib/swifter_enum/base.rb`): Base class for all enum implementations. Key methods:
27
+ - `set_values`: Defines enum values (accepts hash of symbol->integer or array of symbols/strings)
28
+ - Instance comparison via `==` supports symbols, strings, and other enum instances
29
+ - `t` method for i18n translations
30
+ - `in?` method for collection checking
31
+
32
+ 2. **SwifterEnum Module** (`lib/swifter_enum/swifter_enum.rb`): ActiveSupport::Concern that adds `swifter_enum` class method to ActiveRecord models. Creates:
33
+ - Getter returning enum class instance
34
+ - Setter accepting enum instances or raw values
35
+ - `_raw` getter/setter for backward compatibility with standard Rails enums
36
+ - `_raws` class method returning the value mappings
37
+
38
+ 3. **SwifterEnumValidator** (`lib/swifter_enum/swifter_enum_validator.rb`): Custom validator for enum attributes
39
+
40
+ 4. **Generator** (`lib/swifter_enum/generators/enum/enum_generator.rb`): Rails generator for creating new enum classes in `app/models/swifter_enum/`
41
+
42
+ ### Integration Pattern
43
+
44
+ Models use `swifter_enum :attribute_name, EnumClass` instead of standard Rails `enum`. This maintains database compatibility while returning enum class instances that can have custom methods.
45
+
46
+ The gem maintains full backward compatibility through `_raw` accessors, making it easy to migrate existing Rails enums incrementally.
data/README.md CHANGED
@@ -1,103 +1,283 @@
1
1
  # SwifterEnum
2
2
 
3
- SwifterEnum is a Ruby gem for creating enumerated types (enums) in Ruby on Rails applications.
4
-
5
- It is inspired by Swift's enums, and allows you to keep logic related to your enum directly in your enum class.
6
-
7
- so - after defining
8
-
9
- class Video < ApplicationRecord
10
- swifter_enum :camera, CameraEnum
3
+ SwifterEnum brings Swift-style enums to Ruby on Rails, allowing you to encapsulate behavior within your enum types rather than scattering it throughout your application.
4
+
5
+ ## Why SwifterEnum?
6
+
7
+ In Swift, enums are first-class types that can have methods, computed properties, and associated values. SwifterEnum brings this powerful pattern to Rails by replacing simple string/integer enums with proper objects that can carry their own behavior.
8
+
9
+ ### Key Benefits
10
+
11
+ **1. Encapsulated Behavior** - Your enum knows how to handle itself:
12
+
13
+ ```ruby
14
+ # Define your enum with its behavior
15
+ class PaymentStatusEnum < SwifterEnum::Base
16
+ set_values ({
17
+ pending: 0,
18
+ processing: 10,
19
+ completed: 20,
20
+ failed: 30,
21
+ refunded: 40
22
+ })
23
+
24
+ def completed?
25
+ [:completed, :refunded].include?(value)
26
+ end
27
+
28
+ def can_refund?
29
+ value == :completed
30
+ end
31
+
32
+ def icon
33
+ case value
34
+ when :pending then "clock"
35
+ when :processing then "spinner"
36
+ when :completed then "check-circle"
37
+ when :failed then "x-circle"
38
+ when :refunded then "rotate-left"
11
39
  end
40
+ end
41
+
42
+ def color
43
+ case value
44
+ when :pending then "gray"
45
+ when :processing then "blue"
46
+ when :completed then "green"
47
+ when :failed then "red"
48
+ when :refunded then "orange"
49
+ end
50
+ end
51
+ end
52
+
53
+ # Use it naturally in your models
54
+ order.payment_status.completed? # => true
55
+ order.payment_status.can_refund? # => true
56
+ order.payment_status.icon # => "check-circle"
57
+ order.payment_status.color # => "green"
58
+ ```
59
+
60
+ **2. Type Safety** - Catch invalid values at runtime:
61
+
62
+ ```ruby
63
+ # Safe access with bracket notation - raises error for invalid values
64
+ status = PaymentStatusEnum[:completed] # ✓ Returns enum instance
65
+ status = PaymentStatusEnum[:invalid] # ✗ Raises ArgumentError
66
+
67
+ # Validation in models
68
+ class Order < ApplicationRecord
69
+ swifter_enum :payment_status, PaymentStatusEnum
70
+ validates :payment_status, swifter_enum: true
71
+ end
72
+ ```
73
+
74
+ **3. Smart Enum Objects with Flexible Equality** - Returns enum instances that work naturally with symbols and strings:
75
+
76
+ ```ruby
77
+ order.payment_status # => #<PaymentStatusEnum @value=:completed>
78
+
79
+ # Flexible equality checking - all of these work:
80
+ order.payment_status == :completed # => true (symbol comparison)
81
+ order.payment_status == "completed" # => true (string comparison)
82
+ order.payment_status == other.payment_status # => true (enum instance comparison)
83
+
84
+ # Use in case statements naturally
85
+ case order.payment_status
86
+ when :pending then "Waiting for payment"
87
+ when :completed then "All done!"
88
+ when :failed then "Something went wrong"
89
+ end
90
+
91
+ # Or in arrays
92
+ [:completed, :refunded].include?(order.payment_status) # => true
93
+ ```
94
+
95
+ **4. Seamless Rails Integration** - All standard Rails enum features continue to work:
96
+
97
+ ```ruby
98
+ # Familiar Rails enum syntax
99
+ class Order < ApplicationRecord
100
+ swifter_enum :payment_status, PaymentStatusEnum
101
+ swifter_enum :priority, PriorityEnum
102
+ end
103
+
104
+ # Standard Rails enum features work unchanged:
105
+ order.payment_status = :processing # Set with symbol, string, or enum instance
106
+ order.payment_status = "processing" # String works too
107
+ order.payment_status = PaymentStatusEnum[:processing] # Or enum instance
108
+ order.completed! # Bang method sets and saves
109
+ order.completed? # => true (query method)
110
+ order.not_completed? # => false (negative query)
111
+
112
+ Order.completed # Scope returning all completed orders
113
+ Order.not_completed # Scope returning all non-completed orders
114
+ ```
115
+
116
+ **5. Progressive Migration** - Adopt incrementally without breaking existing code:
117
+
118
+ ```ruby
119
+ order.payment_status # => #<PaymentStatusEnum @value=:completed>
120
+
121
+ # Both setters work
122
+ order.payment_status = :pending # Symbol
123
+ order.payment_status = PaymentStatusEnum[:pending] # Enum instance
124
+
125
+ # Raw methods provide an 'escape hatch' to standard Rails enum handling
126
+ order.payment_status_raw # => "completed" (original Rails enum value)
127
+ Order.payment_status_raws # => {"pending"=>0, "processing"=>10, ...}
128
+ ```
129
+
130
+ ## Real-World Example
131
+
132
+ Consider a subscription system where enum behavior naturally belongs with the enum itself:
133
+
134
+ ```ruby
135
+ class SubscriptionTierEnum < SwifterEnum::Base
136
+ set_values ({
137
+ free: 0,
138
+ basic: 10,
139
+ pro: 20,
140
+ enterprise: 30
141
+ })
142
+
143
+ def price
144
+ case value
145
+ when :free then 0
146
+ when :basic then 9.99
147
+ when :pro then 29.99
148
+ when :enterprise then 99.99
149
+ end
150
+ end
151
+
152
+ def features
153
+ case value
154
+ when :free then ["5 projects", "Basic support"]
155
+ when :basic then ["20 projects", "Email support", "API access"]
156
+ when :pro then ["Unlimited projects", "Priority support", "Advanced API"]
157
+ when :enterprise then ["Everything in Pro", "SSO", "Dedicated support", "SLA"]
158
+ end
159
+ end
160
+
161
+ def can_upgrade_to?(other_tier)
162
+ return false unless other_tier.is_a?(self.class)
163
+ self.class.values[other_tier.value] > self.class.values[value]
164
+ end
165
+
166
+ def badge_color
167
+ case value
168
+ when :free then "gray"
169
+ when :basic then "blue"
170
+ when :pro then "purple"
171
+ when :enterprise then "gold"
172
+ end
173
+ end
174
+ end
12
175
 
13
- you can then define and access methods on your enum like
176
+ # Clean, expressive code in your views and controllers
177
+ current_user.subscription_tier.price # => 29.99
178
+ current_user.subscription_tier.features # => ["Unlimited projects", ...]
179
+ current_user.subscription_tier.can_upgrade_to?(SubscriptionTierEnum[:enterprise]) # => true
14
180
 
15
- `video.camera.icon`
181
+ # In your views
182
+ <span class="badge badge-<%= current_user.subscription_tier.badge_color %>">
183
+ <%= current_user.subscription_tier.t %>
184
+ </span>
185
+ <ul>
186
+ <% current_user.subscription_tier.features.each do |feature| %>
187
+ <li><%= feature %></li>
188
+ <% end %>
189
+ </ul>
190
+ ```
16
191
 
17
- This avoids helper methods which distribute your enum logic around your application.
192
+ This approach eliminates helper methods, reduces case statements scattered across your codebase, and keeps related logic together where it belongs.
18
193
 
19
- **Before**
20
194
 
21
- helper method somewhere in the app
195
+ ## Installation
22
196
 
23
- #app/helpers/controller_helper.rb
197
+ Add this line to your application's Gemfile:
24
198
 
25
- def icon_for(camera:)
26
- ...
27
- end
199
+ gem 'swifter_enum'
28
200
 
29
- called with
201
+ ## Usage
30
202
 
31
- icon_for(camera:my_model.camera)
203
+ ### Basic Setup
32
204
 
33
- **After**
205
+ Define your enum class inheriting from `SwifterEnum::Base`:
34
206
 
35
- logic encapsluated within the enum class
207
+ ```ruby
208
+ class CameraEnum < SwifterEnum::Base
209
+ # Using integers for database storage (most common)
210
+ set_values ({ videographer: 0, handcam: 1 })
36
211
 
37
- #app/models/swifter_enum/camera_enum.rb
38
- class CameraEnum < SwifterEnum::Base
39
- set_values ({ videographer: 0, handcam: 1 })
212
+ # Or use strings for database storage (see "Using string values to store Enum" section)
213
+ # set_values [:videographer, :handcam]
40
214
 
41
- def icon
42
- ...
43
- end
215
+ def icon
216
+ case @value
217
+ when :videographer then "icons/video-camera"
218
+ when :handcam then "icons/hand-stop"
44
219
  end
220
+ end
221
+ end
222
+ ```
45
223
 
46
- called with
47
-
48
- my_model.camera.icon
49
-
50
- I was prompted to create this gem by reading about enum approaches in [the RailsNotes Newsletter](https://railsnotes.beehiiv.com/p/issue-17-enums-value-objects-field-guide-enum-sort-in-order-of). Like any good programmer, none of those solutions *quite* met my requirements. Hopefully it will be useful. I welcome feedback, fixes and pull requests.
224
+ Use it in your model:
51
225
 
226
+ ```ruby
227
+ class Video < ApplicationRecord
228
+ swifter_enum :camera, CameraEnum
52
229
 
53
- ## Installation
54
-
55
- Add this line to your application's Gemfile:
56
-
57
- gem 'swifter_enum'
230
+ # Optional validation
231
+ validates :camera, swifter_enum: true
232
+ end
233
+ ```
58
234
 
59
- ## Usage
235
+ ### Working with Enum Values
60
236
 
61
- ### Overview
237
+ ```ruby
238
+ video = Video.first
62
239
 
240
+ # Getter returns an enum instance (not a string/symbol)
241
+ video.camera # => #<CameraEnum:0x000... @value=:handcam>
242
+ video.camera.value # => :handcam (access underlying symbol)
63
243
 
64
- SwifterEnums act like a normal Rails enum - except that instead of returning string values, they return an instance of your selected class.
244
+ # Multiple ways to set values
245
+ video.camera = :videographer # Symbol
246
+ video.camera = "videographer" # String
247
+ video.camera = CameraEnum[:videographer] # Enum instance
248
+ video.camera = other_video.camera # Copy from another instance
65
249
 
66
- They also have various affordances so that in many cases, you can treat them as if they return symbol values.
250
+ # Call your custom methods
251
+ video.camera.icon # => "icons/video-camera"
67
252
 
68
- We have a Video ActiveModel with an enum defined by
253
+ # Works naturally with conditionals
254
+ if video.camera == :handcam
255
+ # Do something
256
+ end
257
+ ```
69
258
 
70
- class Video < ApplicationRecord
71
- swifter_enum :camera, CameraEnum
72
- end
259
+ ### Safe Value Access with Bracket Notation
73
260
 
74
- CameraEnum is a class like the following
261
+ You can use bracket notation to safely access enum values, which will raise an error if you try to access a non-existent value:
75
262
 
76
- class CameraEnum < SwifterEnum::Base
77
- set_values ({ videographer: 0, handcam: 1 })
263
+ # Access enum values with bracket notation
264
+ emotion = EmotionEnum[:happy]
265
+ emotion => #<EmotionEnum:0x0000000134c7c290 @value=:happy>
78
266
 
79
- def icon
80
- case @value
81
- when :videographer
82
- "icons/video-camera"
83
- when :handcam
84
- "icons/hand-stop"
85
- end
86
- end
87
- end
267
+ # Raises ArgumentError for invalid values
268
+ EmotionEnum[:invalid] # ArgumentError: Unknown enum value: :invalid
88
269
 
89
- This provides a richer approach to enums:
270
+ # Must use symbols, not strings
271
+ EmotionEnum["happy"] # ArgumentError: Enum key must be a Symbol, got String
90
272
 
91
- v = Video.first
92
- v.camera => #<CameraEnum:0x0000000134c7c290 @value=:handcam>
93
- v.camera.value => :handcam
273
+ This is useful when you want to ensure you're only using valid enum values, such as when processing user input or configuration:
94
274
 
95
- #you can set values directly
96
- v.camera = :videographer
97
- v.camera => #<CameraEnum:0x000000013385f078 @value=:videographer>
275
+ # Example: Setting a model attribute with validation
276
+ video.camera = CameraEnum[:videographer] # Safe - will raise if :videographer doesn't exist
98
277
 
99
- #the purpose of this gem is that you can now define and access methods on the CameraEnum
100
- v.camera.icon => "icons/video-camera"
278
+ # Example: Using with dynamic values
279
+ status_key = params[:status].to_sym
280
+ model.status = StatusEnum[status_key] # Will raise error if status_key is invalid
101
281
 
102
282
 
103
283
  ### Using Enums in ActiveRecord Models
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- swifter_enum (0.9.7)
4
+ swifter_enum (1.0.0)
5
5
  activemodel (>= 7.0, < 9.0)
6
6
  activerecord (>= 7.0, < 9.0)
7
7
  activesupport (>= 7.0, < 9.0)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- swifter_enum (0.9.7)
4
+ swifter_enum (1.0.0)
5
5
  activemodel (>= 7.0, < 9.0)
6
6
  activerecord (>= 7.0, < 9.0)
7
7
  activesupport (>= 7.0, < 9.0)
@@ -19,6 +19,18 @@ module SwifterEnum
19
19
  @values.keys.map { |key| new(key) }
20
20
  end
21
21
 
22
+ def [](key)
23
+ unless key.is_a?(Symbol)
24
+ raise ArgumentError, "Enum key must be a Symbol, got #{key.class}"
25
+ end
26
+
27
+ unless @values.key?(key)
28
+ raise ArgumentError, "Unknown enum value: :#{key}. Valid values are: #{@values.keys.map { |k| ":#{k}" }.join(", ")}"
29
+ end
30
+
31
+ new(key)
32
+ end
33
+
22
34
  private
23
35
 
24
36
  def validate_array_elements!(array)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwifterEnum
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swifter_enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Jonson
@@ -125,10 +125,11 @@ dependencies:
125
125
  - - "~>"
126
126
  - !ruby/object:Gem::Version
127
127
  version: '2.4'
128
- description: Simple enum for active record that takes inspiration from Swift's enums
129
- to allow you to encapsulate enum logic within an enum class. This is easy to drop-in
130
- as a replacement for regular rails enums, with minimal changes required. Once you
131
- switch, then you can easily extend your enums with methods.
128
+ description: 'SwifterEnum transforms Rails enums from simple values into powerful
129
+ objects with methods, computed properties, and type safety. Your enums become smart:
130
+ payment_status.can_refund?, subscription.price, status.icon - all while maintaining
131
+ 100% Rails enum compatibility. Drop-in replacement that eliminates scattered helper
132
+ methods and case statements throughout your codebase.'
132
133
  email:
133
134
  - rob@hobbyistsoftware.com
134
135
  executables: []
@@ -138,6 +139,7 @@ files:
138
139
  - ".standard.yml"
139
140
  - Appraisals
140
141
  - CHANGELOG.md
142
+ - CLAUDE.md
141
143
  - LICENSE.txt
142
144
  - README.md
143
145
  - Rakefile
@@ -154,7 +156,6 @@ files:
154
156
  - lib/swifter_enum/swifter_enum_validator.rb
155
157
  - lib/swifter_enum/version.rb
156
158
  - sig/swifter_enum.rbs
157
- - swifter_enum.gemspec
158
159
  homepage: https://github.com/ConfusedVorlon/SwifterEnum
159
160
  licenses:
160
161
  - MIT
@@ -178,5 +179,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
179
  requirements: []
179
180
  rubygems_version: 3.6.8
180
181
  specification_version: 4
181
- summary: Active Record enum that uses a class, so you can add methods.
182
+ summary: Swift-style enums for Rails that encapsulate behavior within your enum types
182
183
  test_files: []
data/swifter_enum.gemspec DELETED
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/swifter_enum/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "swifter_enum"
7
- spec.version = SwifterEnum::VERSION
8
- spec.authors = ["Rob Jonson"]
9
- spec.email = ["rob@hobbyistsoftware.com"]
10
-
11
- spec.summary = "Active Record enum that uses a class, so you can add methods."
12
- spec.description = "Simple enum for active record that takes inspiration from Swift's enums to allow you to encapsulate enum logic within an enum class. This is easy to drop-in as a replacement for regular rails enums, with minimal changes required. Once you switch, then you can easily extend your enums with methods."
13
- spec.homepage = "https://github.com/ConfusedVorlon/SwifterEnum"
14
- spec.license = "MIT"
15
-
16
- # I'm using 3.2 and above. If you're willing/able to test on lower rubies, then please let me know and feel free to change this.
17
- spec.required_ruby_version = ">= 3.2.0"
18
-
19
- spec.metadata["homepage_uri"] = spec.homepage
20
- spec.metadata["source_code_uri"] = "https://github.com/ConfusedVorlon/SwifterEnum"
21
- spec.metadata["changelog_uri"] = "https://github.com/ConfusedVorlon/SwifterEnum"
22
-
23
- # Specify which files should be added to the gem when it is released.
24
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
- spec.files = Dir.chdir(__dir__) do
26
- `git ls-files -z`.split("\x0").reject do |f|
27
- (File.expand_path(f) == __FILE__) ||
28
- f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
29
- end
30
- end
31
- spec.bindir = "exe"
32
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
- spec.require_paths = ["lib"]
34
-
35
- spec.add_dependency "activerecord", ">= 7.0", "< 9.0"
36
- spec.add_dependency "activesupport", ">= 7.0", "< 9.0"
37
- spec.add_dependency "activemodel", ">= 7.0", "< 9.0"
38
-
39
- # Specify development dependencies
40
- spec.add_development_dependency "sqlite3", ">= 1.4"
41
- spec.add_development_dependency "minitest", "~> 5.22"
42
- spec.add_development_dependency "debug"
43
- spec.add_development_dependency "appraisal", "~> 2.4"
44
-
45
- # For more information and examples about making a new gem, check out our
46
- # guide at: https://bundler.io/guides/creating_gem.html
47
- end