accessor_hooks 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 93a37b5efd529c2d7a04f33cc8c55446036e08bf7aa5e77649e1201f2796735a
4
+ data.tar.gz: 3a9cf292c31f18cbfe40605e66843ff04e6bb0da968f14fd523b8223fe17f7fb
5
+ SHA512:
6
+ metadata.gz: 488dfa71bd9c58e48ee4a6e1e50ada205fd655ecf5a806c51cd3c3db929d415b56c2a4ce34defa821af3f05e9fc77479593567c847d02114b6c3fb6a53dd2be5
7
+ data.tar.gz: 512469d74a2ea13c441aa1660f81acfb450357eebd97df02a351b99294e7acc3bfd1e21e38ff5b695b8f02678c7269dad9b7e5fe5b2bf7fa182290ee0ea700d1
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ TargetRubyVersion: 3.0
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Metrics/MethodLength:
13
+ Enabled: false
14
+
15
+ Metrics/BlockLength:
16
+ Enabled: false
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+
21
+ Gemspec/RequireMFA:
22
+ Enabled: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Suban05
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # AccessorHooks
2
+
3
+ `AccessorHooks` is a Ruby module that allows you to define hooks (`before_change` and `after_change`) on attribute writers. These hooks can be used to execute custom logic when an attribute is modified.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'accessor_hooks'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```sh
16
+ bundle install
17
+ ```
18
+
19
+ Or install it manually:
20
+
21
+ ```sh
22
+ gem install accessor_hooks
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Include `AccessorHooks` in your class and define hooks using `before_change` and `after_change`.
28
+
29
+ ### Basic Example
30
+
31
+ ```ruby
32
+ class User
33
+ include AccessorHooks
34
+
35
+ attr_reader :full_name
36
+ attr_accessor :first_name, :last_name
37
+
38
+ after_change :update_full_name, on: %i[first_name last_name]
39
+
40
+ private
41
+
42
+ def update_full_name
43
+ @full_name = [first_name, last_name].join(" ").strip
44
+ end
45
+ end
46
+
47
+ user = User.new
48
+ user.first_name = "John"
49
+ puts user.full_name # "John"
50
+
51
+ user.last_name = "Doe"
52
+ puts user.full_name # "John Doe"
53
+ ```
54
+
55
+ ### Using `before_change`
56
+
57
+ `before_change` hooks run before the attribute value is updated.
58
+
59
+ ```ruby
60
+ class Document
61
+ include AccessorHooks
62
+
63
+ attr_reader :name
64
+ attr_accessor :title
65
+
66
+ before_change :clear_name, on: :title
67
+
68
+ private
69
+
70
+ def clear_name
71
+ @name = ""
72
+ end
73
+ end
74
+
75
+ doc = Document.new
76
+ doc.title = "New Title"
77
+ puts doc.name # ""
78
+ ```
79
+
80
+ ### Passing the New Attribute Value to the Hook
81
+
82
+ The new value of the attribute can be passed to the hook method.
83
+
84
+ ```ruby
85
+ class FileEntity
86
+ include AccessorHooks
87
+
88
+ attr_reader :file_name
89
+ attr_accessor :name
90
+
91
+ after_change :update_file_name, on: :name
92
+
93
+ private
94
+
95
+ def update_file_name(name)
96
+ @file_name = "#{name}.pdf"
97
+ end
98
+ end
99
+
100
+ file = FileEntity.new
101
+ file.name = "document"
102
+ puts file.file_name # "document.pdf"
103
+ ```
104
+
105
+ ### Combining `before_change` and `after_change`
106
+
107
+ ```ruby
108
+ class Record
109
+ include AccessorHooks
110
+
111
+ attr_reader :ids
112
+ attr_accessor :id
113
+
114
+ before_change :validate_id, on: :id
115
+ after_change :store_id, on: :id
116
+
117
+ def initialize
118
+ @ids = []
119
+ end
120
+
121
+ private
122
+
123
+ def validate_id(value)
124
+ raise StandardError, "ID cannot be negative" if value < 0
125
+ end
126
+
127
+ def store_id
128
+ @ids << @id
129
+ end
130
+ end
131
+
132
+ record = Record.new
133
+ record.id = 1
134
+ puts record.ids.inspect # [1]
135
+
136
+ begin
137
+ record.id = -1 # Raises StandardError
138
+ rescue StandardError => e
139
+ puts e.message
140
+ end
141
+ ```
142
+
143
+ ## Running Tests
144
+
145
+ Run the test suite using RSpec:
146
+
147
+ ```sh
148
+ bundle exec rspec
149
+ ```
150
+
151
+ ## License
152
+
153
+ This project is licensed under the MIT License.
154
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RuboCop::RakeTask.new
8
+ RSpec::Core::RakeTask.new(:rspec)
9
+
10
+ task default: %i[rspec rubocop]
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AccessorHooks
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "accessor_hooks/version"
4
+
5
+ module AccessorHooks
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.instance_variable_set(:@event_hooks, {})
9
+ end
10
+
11
+ module ClassMethods
12
+ def before_change(hook, on:)
13
+ extend_writers(hook, :before_change, on)
14
+ end
15
+
16
+ def after_change(hook, on:)
17
+ extend_writers(hook, :after_change, on)
18
+ end
19
+
20
+ def event_hooks
21
+ @event_hooks
22
+ end
23
+
24
+ private
25
+
26
+ def extend_writers(hook, event, attributes)
27
+ Array(attributes).each do |attr|
28
+ @event_hooks[attr] ||= {}
29
+ if @event_hooks[attr].empty?
30
+ save_original_writer(attr)
31
+ extend_writer(attr)
32
+ end
33
+ @event_hooks[attr][event] = hook
34
+ end
35
+ end
36
+
37
+ def extend_writer(attr)
38
+ writer = writer_name(attr)
39
+ define_method(:"#{attr}=") do |value|
40
+ run_hook(attr, :before_change, value)
41
+ send(writer, value)
42
+ run_hook(attr, :after_change, value)
43
+ end
44
+ end
45
+
46
+ def save_original_writer(attr)
47
+ writer = writer_name(attr)
48
+ alias_method writer, :"#{attr}="
49
+ private writer
50
+ end
51
+
52
+ def writer_name(attr)
53
+ :"set_#{attr}"
54
+ end
55
+ end
56
+
57
+ def run_hook(attr, event, value)
58
+ hook = self.class.event_hooks.dig(attr, event)
59
+ return unless hook && respond_to?(hook, true)
60
+
61
+ args = method(hook).arity == 1 ? [value] : []
62
+ send(hook, *args)
63
+ end
64
+
65
+ private :run_hook
66
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: accessor_hooks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Anatoly Busygin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-02-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Provides before and after hooks for attribute changes in Ruby classes,
14
+ allowing easy execution of custom logic when attributes are modified.
15
+ email:
16
+ - anatolyb94@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".rubocop.yml"
22
+ - LICENSE.txt
23
+ - README.md
24
+ - Rakefile
25
+ - lib/accessor_hooks.rb
26
+ - lib/accessor_hooks/version.rb
27
+ homepage: https://github.com/Suban05/accessor_hooks
28
+ licenses:
29
+ - MIT
30
+ metadata:
31
+ homepage_uri: https://github.com/Suban05/accessor_hooks
32
+ source_code_uri: https://github.com/Suban05/accessor_hooks
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 3.0.0
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.5.22
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Accessor hooks for Ruby attributes
52
+ test_files: []