active_module 0.1.7 → 0.2.0

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: 191f7c2deb3a07261247a4302436cc734541ab62c3db9dd742974d58320dd763
4
- data.tar.gz: bd20cb4d1aa7b6437facfaefc8213485571497df5e9e607ce19d16b81db246b8
3
+ metadata.gz: 743570de05b5e510d10408fe3b95eaab2789e4432311538bda978bf52d893355
4
+ data.tar.gz: 72057e516212b80260b45fe9b2bd5c97530fc5f6ceaab42c0578d48581bf3b50
5
5
  SHA512:
6
- metadata.gz: bc48c3f11e8f71321ab4abece1b5d58f671c96de7e494a5f2f7a8e55b9946c6e88af9e61970bdd2332eba12ee74dadec13485555dff0f05a4f74209c1327fd96
7
- data.tar.gz: 126c9c4a2d885cadbe472dc23caa5025415f2806411e9eecce37a5b40c28ce990c5956246646292e9780b6f30d6f77705ab681899a7532940dbe0cfe59f2a471
6
+ metadata.gz: 7c4ee2bc7ae24fb246f9744f161088222af6c02d85c99d62e39f196257b90d24abcea21de0217ea578d8755acfd61bf4d078d9f7290f3cc0cc28110bca893cd9
7
+ data.tar.gz: '02582a7cbe08a616e35828e0537bacf41714cd4cfae0d5272188fb9feb8aa970c44670391b1cef6926781ebe53f28ce966a0eeda46d32e2149be22370cf19daf'
data/README.md CHANGED
@@ -1,21 +1,23 @@
1
- # ActiveModule
2
1
 
3
- *"Let's turn modules and classes into first-class active record values!"*
2
+ # active_module
3
+ ![alt text](https://img.shields.io/badge/coverage-100%25-brightgreen)
4
+
5
+ #### *Modules and Classes as first-class active record values!*
4
6
 
5
7
  ActiveModel/ActiveRecord implementation of the Module attribute type.
6
8
 
7
- - Allows storing a reference to a `Module` or `Class` (because they are modules) in a `:string`
8
- database field in a safe and efficient way.
9
- - It automatically casts strings and symbols into modules when creating and querying objects.
10
- - Symbols or strings refer to the modules using unqualified names.
9
+ - Allows storing a reference to a `Module` or `Class` in a `:string` database field
10
+ - Automatically casts strings and symbols into modules when creating and querying objects
11
+ - Symbols or strings refer to the modules using unqualified names
12
+ - It is safe and efficient
11
13
 
12
14
  This is a very generic mechanism that enables many possible utilizations, for instance:
13
- - Strategy Pattern (composition-based polymorphism)
14
- - Rapid prototyping static domain objects
15
- - Static configuration management
16
- - Rich Java/C#-like enums
15
+ - **Composition-based polymorphism (Strategy design pattern)**
16
+ - **Rapid prototyping static domain objects**
17
+ - **Static configuration management**
18
+ - **Rich Java/C#-like enums**
17
19
 
18
- You can find examples of these in [Usage -> Examples :](#Examples)
20
+ You can find examples of these in [Usage -> Examples](#Examples).
19
21
 
20
22
  ## TL;DR
21
23
 
@@ -36,13 +38,24 @@ object.module_field = "Module"
36
38
  object.module_field #=> Nested::Module:Module
37
39
  ```
38
40
 
39
- And compare them like this (optional):
41
+ Query them like this:
42
+ ```ruby
43
+ MyARObject.where(module_field: Nested::Module)
44
+ MyARObject.where(module_field: :Module)
45
+ MyARObject.where(module_field: "Module")
46
+ object.module_field #=> Nested::Module:Module
47
+ ```
48
+
49
+ And compare them like this:
50
+
40
51
  ```ruby
52
+ object.module_field == Nested::Module
53
+
41
54
  module MyNameSpace
42
55
  using ActiveModule::Comparison
43
56
 
44
- object.module_field =~ :Module1
45
- object.module_field =~ "Module1"
57
+ object.module_field =~ :Module
58
+ object.module_field =~ "Module"
46
59
  end
47
60
  ```
48
61
 
@@ -51,7 +64,7 @@ end
51
64
  Add to your gemfile - and if you are using rails - that's all you need:
52
65
 
53
66
  ```ruby
54
- gem 'active_module', "~>0.1"
67
+ gem 'active_module', "~> 0.2"
55
68
  ```
56
69
 
57
70
  If you are not using rails, just issue this command after loading active record
@@ -73,11 +86,11 @@ ActiveRecord::Type.register(:active_module, ActiveModule::Base)
73
86
  Add a string field to the table you want to hold a module attribute in your migrations
74
87
  ```ruby
75
88
  create_table :my_ar_objects do |t|
76
- t.string :module_field
89
+ t.string :module_field, index: true
77
90
  end
78
91
  ```
79
92
 
80
- Now given this module structure:
93
+ Now given this random module hierarchy:
81
94
  ```ruby
82
95
  class MyARObject < ActiveRecord::Base
83
96
  module MyModule1; end
@@ -95,6 +108,16 @@ class MyARObject < ActiveRecord::Base
95
108
  possible_modules: [MyModule1, MyModule2, MyClass, MyClass::MyModule1]
96
109
  end
97
110
  ```
111
+
112
+ Optionally, you can specify how to map your modules into the database
113
+ (the default is the module's fully qualified name):
114
+ ```ruby
115
+ attribute :module_field,
116
+ :active_module,
117
+ possible_modules: [MyModule1, MyModule2, MyClass, MyClass::MyModule1]
118
+ mapping: {MyModule1 => "this is the db representation of module1"}
119
+ ```
120
+
98
121
  And this is it! Easy!<br>
99
122
 
100
123
  ### Assigning and querying module attributes
@@ -169,23 +192,28 @@ my_ar_object.module_field == MyClass::MyModule1
169
192
 
170
193
  ## Examples
171
194
 
172
- ### Strategy Pattern (composition-based polymorphism)
195
+ ### Composition-based polymorphism (Strategy design pattern)
173
196
 
174
197
  [The Strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern) allows composition based polymorphism. This enables runtime polymorphism (by changing the strategy in runtime),
175
198
  and multiple-polymorphism (by composing an object of multiple strategies).
176
199
 
177
- If you want to use classes this will do:
200
+ If you want to use classes this will do:
178
201
  ```ruby
179
202
  class MyARObject < ActiveRecord::Base
180
- attribute :strategy, :active_module, possible_modules: StrategySuperclass.subclasses
203
+ attribute :strategy_class, :active_module, possible_modules: StrategySuperclass.subclasses
204
+
205
+ def strategy
206
+ @strategy ||= strategy_class.new(some_args_from_the_instance)
207
+ end
181
208
 
182
209
  def run_strategy!(args)
183
- strategy.new(some_args).call(other_args)
210
+ strategy.call(args)
184
211
  end
185
212
  end
186
213
  ```
187
214
 
188
- But if you are not in the mood to define a class hierarchy for it, you can use modules instead:
215
+ But if you are not in the mood to define a class hierarchy for it (or if you are performance-savy),
216
+ you may use modules instead:
189
217
 
190
218
  ```ruby
191
219
  class MyARObject < ActiveRecord::Base
@@ -214,6 +242,39 @@ MyARObject.create!(module_field: :Strategy1).run_strategy! #=> "strategy1 called
214
242
  MyARObject.create!(module_field: :Strategy2).run_strategy! #=> "strategy2 called"
215
243
  ```
216
244
 
245
+ You can later easily promote these modules to classes if you need instance variables:
246
+
247
+ ```ruby
248
+ class MyARObject < ActiveRecord::Base
249
+ class Strategy1
250
+ def self.call
251
+ self.new.call
252
+ end
253
+
254
+ def call
255
+ "strategy1 called"
256
+ end
257
+ end
258
+
259
+ module Strategy2
260
+ def self.call
261
+ "strategy2 called"
262
+ end
263
+ end
264
+
265
+ attribute :strategy,
266
+ :active_module,
267
+ possible_modules: [Strategy1, Strategy2]
268
+
269
+ def run_strategy!(some_args)
270
+ strategy.call(some_args, other_args)
271
+ end
272
+ end
273
+
274
+ MyARObject.create!(module_field: :Strategy1).run_strategy! #=> "strategy1 called"
275
+ MyARObject.create!(module_field: :Strategy2).run_strategy! #=> "strategy2 called"
276
+ ```
277
+
217
278
 
218
279
  ### Rapid prototyping static domain objects
219
280
 
@@ -5,11 +5,35 @@ require_relative "modules_index"
5
5
 
6
6
  module ActiveModule
7
7
  class Base < ActiveModel::Type::Value
8
- def initialize(possible_modules:)
9
- @possible_modules = possible_modules
8
+ attr_reader :possible_modules, :mapping
9
+
10
+ def initialize(possible_modules_or_mapping = nil,
11
+ possible_modules: [],
12
+ mapping: {})
13
+ @possible_modules =
14
+ if possible_modules_or_mapping.is_a?(Array)
15
+ possible_modules_or_mapping + possible_modules
16
+ else
17
+ possible_modules
18
+ end
19
+ mapping_arg = if possible_modules_or_mapping.is_a?(Hash)
20
+ possible_modules_or_mapping.merge(mapping)
21
+ else
22
+ mapping
23
+ end
24
+ @mapping =
25
+ @possible_modules.each_with_object(mapping_arg.dup) do |mod, result|
26
+ result[mod] ||= mod.name
27
+ end
10
28
  super()
11
29
  end
12
30
 
31
+ def ==(other)
32
+ other.is_a?(Base) &&
33
+ possible_modules == other.possible_modules &&
34
+ mapping == other.mapping
35
+ end
36
+
13
37
  def type
14
38
  :active_module
15
39
  end
@@ -38,13 +62,11 @@ module ActiveModule
38
62
  end
39
63
 
40
64
  def serialize(module_instance)
41
- module_instance && cast(module_instance).name
65
+ mapping[cast(module_instance)]
42
66
  end
43
67
 
44
68
  def deserialize(str)
45
- str&.constantize
46
- rescue NameError
47
- nil
69
+ from_db[str]
48
70
  end
49
71
 
50
72
  private
@@ -78,5 +100,9 @@ module ActiveModule
78
100
  def modules_index
79
101
  @modules_index ||= ModulesIndex.new(@possible_modules)
80
102
  end
103
+
104
+ def from_db
105
+ @from_db ||= mapping.invert
106
+ end
81
107
  end
82
108
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModule
4
- VERSION = "0.1.7"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_module
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Rolo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-18 00:00:00.000000000 Z
11
+ date: 2024-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '9'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: '7'
22
+ version: '7.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '9'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '7'
32
+ version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: bundler
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -44,7 +44,7 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.15.0
47
- description: Let's turn modules and classes into first-class active record values!
47
+ description: ActiveModel/ActiveRecord module attribute type implementation
48
48
  email:
49
49
  - pedrorolo@gmail.com
50
50
  executables: []
@@ -89,5 +89,5 @@ requirements: []
89
89
  rubygems_version: 3.5.7
90
90
  signing_key:
91
91
  specification_version: 4
92
- summary: ActiveModel/ActiveRecord module attribute type implementation
92
+ summary: Modules and Classes as first-class active record values
93
93
  test_files: []