with_attributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1cd2b0040307ecf193dd4aeaa8e964a52676f1511b0fae3afe4452eaa27a9eb3
4
+ data.tar.gz: 645c20b445e473ae0063b434140fb8a0de5ae42c0d9b4be5512b71d7cfcb18d8
5
+ SHA512:
6
+ metadata.gz: 3b60ed7e8a7916f117077c4a1bf45cb19ce5760ffe4ebc969eb438ed8454cb721261ca0ccffaa69b145ff21dad9c4555628b27c0363d446b4108d6acad5095a5
7
+ data.tar.gz: 2c0c15826a68fac8af53c54eeca2c4203f3e24f17fccc8979bb915b7f52c81880cba280dfdb2954e094741156cc0dda19ab20ea244b53e050046416f04190380
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Javier Aranda
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # WithAttributes
2
+
3
+ [![Testing](https://github.com/javierav/with_attributes/actions/workflows/testing.yml/badge.svg)](https://github.com/javierav/with_attributes/actions/workflows/testing.yml)
4
+
5
+ Temporarily enabling or disabling boolean attributes on classes and instances using `with` and `without` dynamic methods.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem "with_attributes"
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```shell
18
+ bundle install
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ class User
25
+ extend WithAttributes
26
+
27
+ with_attribute :notifications, default: false
28
+ end
29
+ ```
30
+
31
+ This add the following methods:
32
+
33
+ * `User.with_notifications` for enable notifications in class-level during block execution.
34
+
35
+ * `User.without_notifications` for disable notifications in class-level during block execution.
36
+
37
+ * `User.enable_notifications`for enable notifications in class-level permanently.
38
+
39
+ * `User.disable_notifications`for disable notifications in class-level permanently.
40
+
41
+ * `User.notifications?`for check current status of notifications in class-level, using default value if not inside `with_notifications` or `without_notifications` modifiers.
42
+
43
+ * `user.with_notifications` for enable notifications in instance-level during block execution.
44
+
45
+ * `user.without_notifications` for disable notifications in instance-level during block execution.
46
+
47
+ * `user.enable_notifications`for enable notifications in instance-level permanently.
48
+ - `user.disable_notifications`for disable notifications in instance-level permanently.
49
+
50
+ - `user.notifications?`for check current status of notifications in instance-level, using class-level value if not inside `with_notifications` or `without_notifications` modifiers.
51
+
52
+ Example using class-level methods:
53
+
54
+ ```ruby
55
+ # using default value
56
+ User.notifications? # => false
57
+
58
+ User.with_notifications do
59
+ User.notifications? # => true
60
+
61
+ User.without_notifications do
62
+ User.notifications? # => false
63
+ end
64
+
65
+ User.notifications? # => true
66
+ end
67
+
68
+ User.notifications? # => false
69
+ ```
70
+
71
+ Example using instance level methods:
72
+
73
+ ```ruby
74
+ user = User.new
75
+
76
+ # if notifications is not changed in this instance, using value of class or default if class value is also not changed
77
+ user.notifications? # => false
78
+
79
+ # changing value in class-level
80
+ User.with_notifications do
81
+ user.notifications? # => true
82
+ end
83
+
84
+ # changing value in instance-level
85
+ user.with_notifications do
86
+ user.notifications? # => true
87
+ end
88
+
89
+ # using nested
90
+ User.with_notifications do
91
+ user.without_notifications do
92
+ user.notifications? # => false
93
+ end
94
+ end
95
+ ```
96
+
97
+ ## Real world example
98
+
99
+ ```ruby
100
+ module Trackable
101
+ extend ActiveSupport::Concern
102
+
103
+ class_methods do
104
+ include WithAttributes
105
+ end
106
+
107
+ included do
108
+ with_attribute :tracking, default: true
109
+
110
+ with_options if: :tracking? do
111
+ after_create { ... }
112
+ after_update { ... }
113
+ after_destroy { ... }
114
+ end
115
+ end
116
+ end
117
+
118
+ class User
119
+ include Trackable
120
+ end
121
+
122
+ User.without_tracking do
123
+ User.create(...)
124
+ end
125
+
126
+ User.find(...).tap do |user|
127
+ user.without_tracking do
128
+ user.update(...)
129
+ end
130
+ end
131
+ ```
132
+
133
+
134
+
135
+ ## Thread safety
136
+
137
+ Thread safety is guaranteed using class-level methods. Instance-level methods only are thread safe if each thread not share the same instance with others.
138
+
139
+ ```ruby
140
+ # thread safe example
141
+
142
+ t1 = Thread.new do
143
+ User.without_notifications do
144
+ User.create(...) # user created without notifications
145
+ end
146
+ end
147
+
148
+ t2 = Thread.new do
149
+ User.with_notifications do
150
+ User.create(...) # user created with notifications
151
+ end
152
+ end
153
+
154
+ [t1, t2].map(&:join)
155
+ ```
156
+
157
+ ```ruby
158
+ # thread safe example
159
+
160
+ t1 = Thread.new do
161
+ user = User.find(...)
162
+
163
+ user.without_notifications do
164
+ user.update(...) # user updated without notifications
165
+ end
166
+ end
167
+
168
+ t2 = Thread.new do
169
+ user = User.find(...)
170
+
171
+ user.with_notifications do
172
+ user.update(...) # user updated with notifications
173
+ end
174
+ end
175
+
176
+ [t1, t2].map(&:join)
177
+ ```
178
+
179
+ ```ruby
180
+ # thread unsafe example
181
+
182
+ user = User.new
183
+
184
+ t1 = Thread.new do
185
+ user.without_notifications do
186
+ user.update(...) # unexpected behaviour, can be created with or without notifications
187
+ end
188
+ end
189
+
190
+ t2 = Thread.new do
191
+ user.with_notifications do
192
+ user.update(...) # unexpected behaviour, can be created with or without notifications
193
+ end
194
+ end
195
+
196
+ [t1, t2].map(&:join)
197
+ ```
198
+
199
+ ## Licence
200
+
201
+ Copyright © 2024 Javier Aranda. Released under the terms of the [MIT license](LICENSE).
@@ -0,0 +1,3 @@
1
+ module WithAttributes
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,79 @@
1
+ require_relative "with_attributes/version"
2
+
3
+ module WithAttributes
4
+ def with_attribute(*attrs, default: true)
5
+ raise ArgumentError, "invalid with_attribute default option" unless [true, false].include?(default)
6
+
7
+ attrs.each do |attr|
8
+ raise NameError, "invalid with_attribute name: #{attr}" unless /^[_A-Za-z]\w*$/.match?(attr)
9
+
10
+ class_eval(<<~CODE, __FILE__, __LINE__ + 1)
11
+ def self.#{attr}?
12
+ value = Thread.current["with_attribute_#{attr}_\#{object_id}"]
13
+
14
+ if value.nil?
15
+ if superclass.respond_to?(:#{attr}?)
16
+ superclass.#{attr}?
17
+ else
18
+ #{default}
19
+ end
20
+ else
21
+ value
22
+ end
23
+ end
24
+
25
+ def self.with_#{attr}
26
+ current = Thread.current["with_attribute_#{attr}_\#{object_id}"]
27
+ enable_#{attr}
28
+ yield if block_given?
29
+ ensure
30
+ Thread.current["with_attribute_#{attr}_\#{object_id}"] = current
31
+ end
32
+
33
+ def self.without_#{attr}
34
+ current = Thread.current["with_attribute_#{attr}_\#{object_id}"]
35
+ disable_#{attr}
36
+ yield if block_given?
37
+ ensure
38
+ Thread.current["with_attribute_#{attr}_\#{object_id}"] = current
39
+ end
40
+
41
+ def self.enable_#{attr}
42
+ Thread.current["with_attribute_#{attr}_\#{object_id}"] = true
43
+ end
44
+
45
+ def self.disable_#{attr}
46
+ Thread.current["with_attribute_#{attr}_\#{object_id}"] = false
47
+ end
48
+
49
+ def #{attr}?
50
+ @#{attr}.nil? ? self.class.#{attr}? : @#{attr}
51
+ end
52
+
53
+ def with_#{attr}
54
+ current = @#{attr}
55
+ enable_#{attr}
56
+ yield if block_given?
57
+ ensure
58
+ @#{attr} = current
59
+ end
60
+
61
+ def without_#{attr}
62
+ current = @#{attr}
63
+ disable_#{attr}
64
+ yield if block_given?
65
+ ensure
66
+ @#{attr} = current
67
+ end
68
+
69
+ def enable_#{attr}
70
+ @#{attr} = true
71
+ end
72
+
73
+ def disable_#{attr}
74
+ @#{attr} = false
75
+ end
76
+ CODE
77
+ end
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: with_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Javier Aranda
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: javier@aranda.dev
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - README.md
21
+ - lib/with_attributes.rb
22
+ - lib/with_attributes/version.rb
23
+ homepage: https://github.com/javierav/with_attributes
24
+ licenses:
25
+ - MIT
26
+ metadata:
27
+ changelog_uri: https://github.com/javierav/with_attributes/releases
28
+ homepage_uri: https://github.com/javierav/with_attributes
29
+ source_code_uri: https://github.com/javierav/with_attributes/tree/v0.1.0
30
+ bug_tracker_uri: https://github.com/javierav/with_attributes/issues
31
+ rubygems_mfa_required: 'true'
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.5.23
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: temporarily enabling or disabling boolean attributes on classes and instances
51
+ test_files: []