magic-decorator 0.1.0.1 → 0.3.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: 3b2496bbc27395a1c313aaa44e0c993037437a485595de2ef59f269835238409
4
- data.tar.gz: 6cace80885c21da63511ac98e4a231ed03ffc67ac9567af4d192ad411b0b7149
3
+ metadata.gz: 673fe3ecf73066b97e1076dfb9a41e7fb31732396e9c39a5f97561d1ec2e616f
4
+ data.tar.gz: 44c33204523d4022f8b2b1c1eb28dd51f5a3499d5001cdfae97fff59568d89d1
5
5
  SHA512:
6
- metadata.gz: 51300c4ae7274695352d0c4eab403f378646c3afb966eee38119000080a525b6637e838a57fce9897484f36de6917ebad630f94e25806399ea72e683789616f4
7
- data.tar.gz: 81c9ee0f416defadebe38efb6e944a643b54f4d08d5aadf04062093141f229bc0a1aa26fa5ff293f219873c7e6927aae7cac163d2c66ba520214d9159a77ccc5
6
+ metadata.gz: 309f3a70d8ce2ae726375e59e152a77d044b7ffe52d8f7064e4baefe964aecc90c4a835dbf3e5474a56e0c7de08e0cb0e975e3e65bc3872a2cc2a18f2a401885
7
+ data.tar.gz: 595d311692d8db65bb29e0adc44222a48abea98b7b869a62d6b823cb436801be1317cddfc9c4caacf9a6514ec8cc0a1182a1b26c005f9efe2c52fa0d48cca2fc
data/CHANGELOG.md CHANGED
@@ -1,10 +1,44 @@
1
+ ## [0.3.0] — 2024-10-27
2
+
3
+ ### Added
4
+
5
+ - Improved extendability: one may override `Magic::Decoratable#decorator_base` to be used for lookups.
6
+ - `Magic::Decoratable.classes` for all the decoratables.
7
+
8
+ ### Fixed
9
+
10
+ - Failures on double decoration attempts.
11
+
12
+
13
+ ## [0.2.0] — 2024-10-17
14
+
15
+ ### Changed
16
+
17
+ - For almost any method called on a decorated object, both its result and `yield`ed arguments get decorated.
18
+ Some methods aren’t meant to be decorated though:
19
+ - `deconstruct` & `deconstruct_keys` for _pattern matching_,
20
+ - _converting_ methods: those starting with `to_`,
21
+ - _system_ methods: those starting with `_`.
22
+
23
+ ### Added
24
+
25
+ - `Magic::Decorator::Base.undecorated` to exclude methods from being decorated automagically.
26
+
27
+ #### Default decorators
28
+
29
+ - `EnumerableDecorator` to decorate `Enumerable`s.
30
+ - enables _splat_ operator: `*decorated` ,
31
+ - enables _double-splat_ operator: `**decorated`,
32
+ - enumerating methods yield decorated items.
33
+
34
+
1
35
  ## [0.1.0] — 2024-10-13
2
36
 
3
37
  ### Added
4
38
 
5
39
  - `Magic::Decorator::Base` — a basic decorator class.
6
40
  - `Magic::Decoratable` to be included in decoratable classes.
7
- - `#decorate`,
8
- - `#decorate!`,
9
- - `#decorated`,
10
- - `#decorated?`.
41
+ - `#decorate`,
42
+ - `#decorate!`,
43
+ - `#decorated`,
44
+ - `#decorated?`.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # 🪄 Magic Decorator
2
2
 
3
+ ![GitHub Actions Workflow Status](
4
+ https://img.shields.io/github/actions/workflow/status/Alexander-Senko/magic-decorator/ci.yml
5
+ )
3
6
  ![Code Climate maintainability](
4
7
  https://img.shields.io/codeclimate/maintainability-percentage/Alexander-Senko/magic-decorator
5
8
  )
@@ -47,20 +50,83 @@ person.name # => "John Smith"
47
50
  This module adds three methods to decorate an object.
48
51
  Decorator class is being inferred automatically.
49
52
  When no decorator is found,
50
- * `#decorate` returns `nil`,
51
- * `#decorate!` raises `Magic::Lookup::Error`,
52
- * `#decorated` returns the original object.
53
+ - `#decorate` returns `nil`,
54
+ - `#decorate!` raises `Magic::Lookup::Error`,
55
+ - `#decorated` returns the original object.
53
56
 
54
57
  One can test for the object is actually decorated with `#decorated?`.
55
58
 
56
59
  ```ruby
57
- 'with no decorator for String'.decorated.decorated? # => false
58
- ['with a decorator for Array'].decorated.decorated? # => true
60
+ 'with no decorator for String'.decorated
61
+ .decorated? # => false
62
+ ['with a decorator for Array'].decorated
63
+ .decorated? # => true
64
+ ```
65
+
66
+ ### Extending decorator logic
67
+
68
+ When extending `Magic::Decoratable`, one may override `#decorator_base` to be used for lookup.
69
+
70
+ ```ruby
71
+ class Special::Decorator < Magic::Decorator::Base
72
+ def self.name_for object_class
73
+ "Special::#{object_class}Decorator"
74
+ end
75
+ end
76
+
77
+ module Special::Decoratable
78
+ include Magic::Decoratable
79
+
80
+ private
81
+
82
+ def decorator_base = Special::Decorator
83
+ end
84
+
85
+ class Special::Model
86
+ include Special::Decoratable
87
+ end
88
+
89
+ Special::Model.new.decorate # looks for Special::Decorator descendants
59
90
  ```
60
91
 
61
- #### Magic
92
+ ## 🪄 Magic
62
93
 
63
- `Decoratable` is mixed into `Object` by default. That means that effectively any object is `Decoratable`.
94
+ ### Decoratable scope
95
+
96
+ `Magic::Decoratable` is mixed into `Object` by default. It means that effectively any object is _magically decoratable_.
97
+
98
+ One can use `Magic::Decoratable.classes` to see all the decoratable classes.
99
+
100
+ ### Decoration expansion
101
+
102
+ For almost any method called on a decorated object, both its result and `yield`ed arguments get decorated.
103
+
104
+ ```ruby
105
+ 'with no decorator for String'.decorated.chars
106
+ .decorated? # => false
107
+ ['with a decorator for Array'].decorated.map(&:chars).first.grep(/\S/).group_by(&:upcase).transform_values(&:size).sort_by(&:last).reverse.first(5).map(&:first)
108
+ .decorated? # => true
109
+ ```
110
+
111
+ #### Undecorated methods
112
+
113
+ Some methods aren’t meant to be decorated though:
114
+
115
+ - `deconstruct` & `deconstruct_keys` for _pattern matching_,
116
+ - _converting_ methods: those starting with `to_`,
117
+ - _system_ methods: those starting with `_`.
118
+
119
+ #### `undecorated` modifier
120
+
121
+ `Magic::Decorator::Base.undecorated` can be used to exclude methods from being decorated automagically.
122
+
123
+ ```ruby
124
+ class MyDecorator < Magic::Decorator::Base
125
+ undecorated %i[to_s inspect]
126
+ undecorated :raw_method
127
+ undecorated :m1, :m2
128
+ end
129
+ ```
64
130
 
65
131
  ### Decorator class inference
66
132
 
@@ -72,6 +138,33 @@ powered by [Magic Lookup](
72
138
  For example, `MyNamespace::MyModel.new.decorate` looks for `MyNamespace::MyModelDecorator` first.
73
139
  When missing, it further looks for decorators for its ancestor classes, up to `ObjectDecorator`.
74
140
 
141
+ ### Default decorators
142
+
143
+ #### `EnumerableDecorator`
144
+
145
+ It automagically decorates all its decoratable items.
146
+
147
+ ```ruby
148
+ [1, [2], { 3 => 4 }, '5'].decorated
149
+ .map &:decorated? # => [false, true, true, false]
150
+
151
+ { 1 => 2, [3] => [4] }.decorated.keys
152
+ .map &:decorated? # => [false, true]
153
+ { 1 => 2, [3] => [4] }.decorated.values
154
+ .map &:decorated? # => [false, true]
155
+
156
+ { 1 => 2, [3] => [4] }.decorated[1]
157
+ .decorated? # => false
158
+ { 1 => 2, [3] => [4] }.decorated[[3]]
159
+ .decorated? # => true
160
+ ```
161
+
162
+ ##### Side effects for decorated collections
163
+
164
+ - enables _splat_ operator: `*decorated` ,
165
+ - enables _double-splat_ operator: `**decorated`,
166
+ - enumerating methods yield decorated items.
167
+
75
168
  ## Development
76
169
 
77
170
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnumerableDecorator < Magic::Decorator::Base
4
+ def to_a(...) = super.map &:decorated
5
+ def to_ary(...) = super.map &:decorated
6
+
7
+ def to_h(...) = super.to_h { |*h| h.map! &:decorated }
8
+ def to_hash(...) = super.to_h { |*h| h.map! &:decorated }
9
+ end
@@ -1,14 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/concern'
4
+
3
5
  module Magic
4
6
  module Decoratable
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def classes
11
+ ObjectSpace.each_object(Class)
12
+ .select { _1 < self }
13
+ end
14
+ end
15
+
16
+ extend ClassMethods
17
+
5
18
  def decorate = decorator&.new self
6
- def decorate! = decorate || raise(Lookup::Error.for self, Decorator)
19
+ def decorate! = decorate || raise(Lookup::Error.for self, decorator_base)
7
20
  def decorated = decorate || self
8
21
  def decorated? = false
9
22
 
10
23
  private
11
24
 
12
- def decorator = Decorator.for self.class
25
+ def decorator = decorator_base.for self.class
26
+ def decorator_base = Decorator
13
27
  end
14
28
  end
@@ -5,13 +5,56 @@ require 'delegate'
5
5
  module Magic
6
6
  module Decorator
7
7
  class Base < SimpleDelegator
8
- extend Lookup
8
+ extend Lookup
9
+ include Decoratable
9
10
 
10
11
  class << self
11
12
  def name_for(object_class) = "#{object_class}Decorator"
13
+
14
+ private
15
+
16
+ def undecorated method, *methods
17
+ return [ method, *methods ].map { undecorated _1 } if
18
+ methods.any?
19
+ return undecorated *method if
20
+ method.is_a? Array
21
+ raise TypeError, "#{method} is not a symbol nor a string" unless
22
+ method in Symbol | String
23
+
24
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
25
+ def #{method}(...) = __getobj__.#{method}(...)
26
+ RUBY
27
+ end
12
28
  end
13
29
 
14
30
  def decorated? = true
31
+
32
+ undecorated %i[
33
+ deconstruct
34
+ deconstruct_keys
35
+ ]
36
+
37
+ def method_missing(method, ...)
38
+ return super if method.start_with? 'to_' # converter
39
+ return super if method.start_with? '_' # system
40
+
41
+ if block_given?
42
+ super { |*args| yield *args.map(&:decorated) }
43
+ else
44
+ super
45
+ end.decorated
46
+ rescue NoMethodError => error
47
+ __raise__ error.class.new(
48
+ error.message.sub(self.class.name, __getobj__.class.name),
49
+ error.name,
50
+ error.args,
51
+ # error.private_call?, # FIXME: not implemented in TruffleRuby
52
+
53
+ receiver: __getobj__
54
+ ).tap {
55
+ _1.set_backtrace error.backtrace[2..] # FIXME: use `backtrace_locations` with Ruby 3.4+
56
+ }
57
+ end
15
58
  end
16
59
  end
17
60
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Magic
4
4
  module Decorator
5
- VERSION = '0.1.0.1'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -18,3 +18,5 @@ module Magic
18
18
  def name_for(...) = Base.name_for(...)
19
19
  end
20
20
  end
21
+
22
+ require 'enumerable_decorator'
@@ -0,0 +1,9 @@
1
+ class EnumerableDecorator[unchecked out Element]
2
+ include _Each[Element, void]
3
+ include Enumerable[Element]
4
+
5
+ def to_ary: () -> ::Array[Element]
6
+
7
+ def to_hash: () -> ::Hash[untyped, untyped]
8
+ | [T, U] () { (Element) -> [T, U] } -> ::Hash[T, U]
9
+ end
@@ -1,13 +1,16 @@
1
1
  module Magic
2
2
  module Decoratable
3
- def decorate: -> Decorator?
4
- def decorate!: -> Decorator
5
- def decorated: -> (Decorator | self)
3
+ def self.classes: () -> Array[Class]
6
4
 
7
- def decorated?: -> bool
5
+ def decorate: () -> Decorator?
6
+ def decorate!: () -> Decorator
7
+ def decorated: () -> (Decorator | self)
8
+
9
+ def decorated?: () -> bool
8
10
 
9
11
  private
10
12
 
11
- def decorator: -> Class?
13
+ def decorator: () -> Class?
14
+ def decorator_base: () -> Module
12
15
  end
13
16
  end
@@ -4,6 +4,12 @@ module Magic
4
4
  extend Lookup
5
5
 
6
6
  def decorated?: -> bool
7
+
8
+ private
9
+
10
+ def self.undecorated: (interned) -> Symbol
11
+ | (interned, interned, *interned) -> Array[Symbol]
12
+ | (Array[interned]) -> Array[Symbol]
7
13
  end
8
14
  end
9
15
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magic-decorator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Senko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-10-13 00:00:00.000000000 Z
10
+ date: 2024-10-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: magic-lookup
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  description: 'SimpleDelegator on steroids: automatic delegation, decorator class inference,
27
41
  etc.'
28
42
  email:
@@ -38,11 +52,13 @@ files:
38
52
  - LICENSE.txt
39
53
  - README.md
40
54
  - Rakefile
55
+ - lib/enumerable_decorator.rb
41
56
  - lib/magic/decoratable.rb
42
57
  - lib/magic/decorator.rb
43
58
  - lib/magic/decorator/authors.rb
44
59
  - lib/magic/decorator/base.rb
45
60
  - lib/magic/decorator/version.rb
61
+ - sig/enumerable_decorator.rbs
46
62
  - sig/gem.rbs
47
63
  - sig/magic/decoratable.rbs
48
64
  - sig/magic/decorator.rbs
@@ -53,7 +69,7 @@ licenses:
53
69
  metadata:
54
70
  homepage_uri: https://github.com/Alexander-Senko/magic-decorator
55
71
  source_code_uri: https://github.com/Alexander-Senko/magic-decorator
56
- changelog_uri: https://github.com/Alexander-Senko/magic-decorator/blob/v0.1.0.1/CHANGELOG.md
72
+ changelog_uri: https://github.com/Alexander-Senko/magic-decorator/blob/v0.3.0/CHANGELOG.md
57
73
  rdoc_options: []
58
74
  require_paths:
59
75
  - lib