has_private_attributes 1.0.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: 0c360b8d110bb4512877816d8a03059bddbb14c76a87d0f9c5c2effebb462b60
4
+ data.tar.gz: ff7cd5c56a2bfc5079c31d87323e88144ef1fe6e41f747943c0a7bf25c39aad0
5
+ SHA512:
6
+ metadata.gz: 8836b85d765c234ef8a8b896b3686359623de3a3b62d61476c4d1145ce8febcab93cc732f721c645fcaccdb31aeb11962b24f0d160a61ce697da6e417d540c13
7
+ data.tar.gz: d8459b3b2aa3ee9a061ff658e452d64078a04732d290e3d00dc01b1011a5a4575882a29b78499c0312f7419f034e40fda95f3ebabb523e47573b59cf7daacf7e
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ inherit_gem:
2
+ rubocop-rails_config:
3
+ - config/rails.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 3.0
7
+
8
+ Style/ClassAndModuleChildren:
9
+ EnforcedStyle: nested
10
+
11
+ Lint/Debugger:
12
+ Enabled: true
13
+
14
+ Style/StringLiterals:
15
+ Enabled: true
16
+ EnforcedStyle: single_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-08-16
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 sebi
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,163 @@
1
+ # HasPrivateAttributes 🔒
2
+
3
+ This Ruby gem provides a simple and elegant way to define private attributes in your classes. It allows you to create static, lazy-evaluated, and argument-based private attributes with automatic caching and freezing of the returned values, all in a thread-safe manner.
4
+
5
+ ## Features 🌟
6
+
7
+ - **Static Attributes**: Define private attributes with a fixed, immutable value.
8
+ - **Lazy Attributes**: Define private attributes that are lazily evaluated and cached.
9
+ - **Argument-based Attributes**: Define private attributes that depend on arguments and cache the results.
10
+ - **Inheritance**: Private attributes can be inherited from parent classes.
11
+ - **Freezing**: All returned values are automatically deep-frozen to prevent modification.
12
+ - **Thread Safety**: All operations are thread-safe, allowing for use in multi-threaded environments.
13
+
14
+ ## Installation 📥
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'has_private_attributes'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ ## Usage 🚀
29
+
30
+ Here's a simple example of how to use the `HasPrivateAttributes` gem:
31
+
32
+ ```ruby
33
+ class MyClass
34
+ include HasPrivateAttributes
35
+
36
+ # definition of private attributes
37
+
38
+ private_attribute :static_servers, [
39
+ { ip: '1.1.1.1', location: 'US' },
40
+ { ip: '8.8.8.8', location: 'US' }
41
+ ]
42
+
43
+ private_attribute :lazy_servers do
44
+ [
45
+ { ip: '2.2.2.2', location: 'EU' },
46
+ { ip: '3.3.3.3', location: 'EU' }
47
+ ]
48
+ end
49
+
50
+ private_attribute :servers_by_region do |region|
51
+ case region
52
+ when 'us'
53
+ [
54
+ { ip: '1.1.1.1', location: 'US' },
55
+ { ip: '8.8.8.8', location: 'US' }
56
+ ]
57
+ when 'eu'
58
+ [
59
+ { ip: '2.2.2.2', location: 'EU' }
60
+ ]
61
+ end
62
+ end
63
+
64
+ # usage of private attributes
65
+
66
+ def get_static_servers
67
+ static_servers
68
+ end
69
+
70
+ def get_lazy_servers
71
+ lazy_servers
72
+ end
73
+
74
+ def get_servers_by_region(region)
75
+ servers_by_region(region)
76
+ end
77
+ end
78
+
79
+ instance = MyClass.new
80
+
81
+ puts instance.get_static_servers
82
+ # Output:
83
+ # [
84
+ # { ip: '1.1.1.1', location: 'US' },
85
+ # { ip: '8.8.8.8', location: 'US' }
86
+ # ]
87
+
88
+ puts instance.get_lazy_servers
89
+ # Output:
90
+ # [
91
+ # { ip: '2.2.2.2', location: 'EU' },
92
+ # { ip: '3.3.3.3', location: 'EU' }
93
+ # ]
94
+
95
+ puts instance.get_servers_by_region('us')
96
+ # Output:
97
+ # [
98
+ # { ip: '1.1.1.1', location: 'US' },
99
+ # { ip: '8.8.8.8', location: 'US' }
100
+ # ]
101
+
102
+ puts instance.get_servers_by_region('eu')
103
+ # Output:
104
+ # [
105
+ # { ip: '2.2.2.2', location: 'EU' }
106
+ # ]
107
+ ```
108
+
109
+ ## Examples 💡
110
+
111
+ Here are some more examples of using the `HasPrivateAttributes` gem:
112
+
113
+ ### Static Attributes 🗄️
114
+
115
+ ```ruby
116
+ instance.static_servers # => [{ ip: '1.1.1.1', location: 'US' }, { ip: '8.8.8.8', location: 'US' }]
117
+ instance.static_servers.frozen? # => true
118
+ ```
119
+
120
+ ### Lazy Attributes 🐢
121
+
122
+ ```ruby
123
+ instance.lazy_servers # => [{ ip: '2.2.2.2', location: 'EU' }, { ip: '3.3.3.3', location: 'EU' }]
124
+ instance.lazy_servers.object_id # => 12345678
125
+ instance.lazy_servers.object_id # => 12345678 (same object)
126
+ ```
127
+
128
+ ### Argument-based Attributes 🔍
129
+
130
+ ```ruby
131
+ instance.servers_by_region('us') # => [{ ip: '1.1.1.1', location: 'US' }, { ip: '8.8.8.8', location: 'US' }]
132
+ instance.servers_by_region('eu') # => [{ ip: '2.2.2.2', location: 'EU' }]
133
+ ```
134
+
135
+ ## Thread Safety 🔒
136
+
137
+ All operations in HasPrivateAttributes are thread-safe. This means you can safely use private attributes in multi-threaded environments without worrying about race conditions or data inconsistencies. The gem uses Ruby's `Monitor` and `Mutex` classes to ensure proper synchronization.
138
+
139
+ For example, you can safely access private attributes from multiple threads:
140
+
141
+ ```ruby
142
+ threads = []
143
+ 10.times do
144
+ threads << Thread.new do
145
+ puts instance.get_lazy_servers
146
+ end
147
+ end
148
+ threads.each(&:join)
149
+ ```
150
+
151
+ This will safely initialize and return the lazy servers, even if multiple threads try to access it simultaneously.
152
+
153
+ ## Contributing 🤝
154
+
155
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sebyx07/has_private_attributes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/sebyx07/has_private_attributes/blob/master/CODE_OF_CONDUCT.md).
156
+
157
+ ## License 📄
158
+
159
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
160
+
161
+ ## Code of Conduct 🤵
162
+
163
+ Everyone interacting in the HasPrivateAttributes project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/sebyx07/has_private_attributes/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/lefthook.yml ADDED
@@ -0,0 +1,8 @@
1
+ pre-commit:
2
+ commands:
3
+ rubocop:
4
+ run: bundle exec rubocop -A
5
+ skip:
6
+ - merge
7
+ - rebase
8
+ stage_fixed: true
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HasPrivateAttributes
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'has_private_attributes/version'
4
+ require 'monitor'
5
+
6
+ module HasPrivateAttributes
7
+ # @!macro [attach] included
8
+ # @!extend ClassMethods
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+
13
+ # Provides class-level methods for defining private attributes.
14
+ module ClassMethods
15
+ # @!macro [new] private_attribute
16
+ # @!method $1(*args)
17
+ # Returns the value of the private attribute '$1'.
18
+ # @return [Object] The value of the private attribute.
19
+ # @!visibility private
20
+ # @note This method is thread-safe.
21
+ #
22
+ # @!method self.$1(*args)
23
+ # Returns the class-level value of the private attribute '$1'.
24
+ # @return [Object] The class-level value of the private attribute.
25
+ # @!visibility private
26
+ # @note This method is thread-safe.
27
+
28
+ # Defines a private attribute with the given name, value, and optional block.
29
+ #
30
+ # @param name [Symbol] The name of the private attribute.
31
+ # @param value [Object, Proc] The value of the private attribute, or a Proc that will be used to lazily evaluate the value.
32
+ # @yield [*args] An optional block that will be used to lazily evaluate the value of the private attribute.
33
+ # @return [void]
34
+ # @!macro private_attribute
35
+ def private_attribute(name, value = nil, &block)
36
+ ivar_name = "@#{name}"
37
+
38
+ define_method(name) do |*args|
39
+ _pa_synchronize do
40
+ if instance_variable_defined?(ivar_name) && args.empty?
41
+ instance_variable_get(ivar_name)
42
+ else
43
+ result = if block_given?
44
+ if block.arity.zero?
45
+ instance_exec(&block)
46
+ else
47
+ HasPrivateAttributes.memoize(self, name) do |*memoized_args|
48
+ instance_exec(*memoized_args, &block)
49
+ end.call(*args)
50
+ end
51
+ elsif value.respond_to?(:call)
52
+ instance_exec(&value)
53
+ else
54
+ value
55
+ end
56
+ result = HasPrivateAttributes._pa_deep_freeze(result)
57
+ instance_variable_set(ivar_name, result) if args.empty?
58
+ result
59
+ end
60
+ end
61
+ end
62
+
63
+ private name
64
+
65
+ singleton_class.define_method(name) do |*args|
66
+ @singleton_monitor ||= Monitor.new
67
+ @singleton_monitor.synchronize do
68
+ @singleton_values ||= {}
69
+ return @singleton_values[name] if @singleton_values.key?(name) && args.empty?
70
+
71
+ result = if block_given?
72
+ if block.arity.zero?
73
+ class_exec(&block)
74
+ else
75
+ HasPrivateAttributes.memoize(self, name) do |*memoized_args|
76
+ class_exec(*memoized_args, &block)
77
+ end.call(*args)
78
+ end
79
+ elsif value.respond_to?(:call)
80
+ class_exec(&value)
81
+ else
82
+ value
83
+ end
84
+
85
+ result = HasPrivateAttributes._pa_deep_freeze(result)
86
+ @singleton_values[name] = result if args.empty?
87
+ result
88
+ end
89
+ end
90
+
91
+ private_class_method name
92
+ end
93
+ end
94
+
95
+ # @!macro [new] utility_method
96
+ # @!visibility private
97
+
98
+ # Deeply freezes the given object, recursively freezing any nested data structures.
99
+ #
100
+ # @param obj [Object] The object to be deeply frozen.
101
+ # @return [Object] The deeply frozen object.
102
+ # @!macro utility_method
103
+ def self._pa_deep_freeze(obj)
104
+ case obj
105
+ when Hash
106
+ obj.each_with_object({}) { |(k, v), memo| memo[_pa_deep_freeze(k)] = _pa_deep_freeze(v) }.freeze
107
+ when Array
108
+ obj.map { |v| _pa_deep_freeze(v) }.freeze
109
+ when String, Numeric, TrueClass, FalseClass, NilClass
110
+ obj
111
+ else
112
+ obj.clone.freeze
113
+ end
114
+ end
115
+
116
+ # Memoizes the result of the given block, keyed by the arguments passed to the block.
117
+ #
118
+ # @param target [Object] The object on which to memoize the result.
119
+ # @param name [Symbol] The name of the memoized value.
120
+ # @yield [*args] The block whose result should be memoized.
121
+ # @return [Proc] A lambda that will return the memoized result.
122
+ # @!macro utility_method
123
+ def self.memoize(target, name)
124
+ target.instance_variable_set("@#{name}_memo", {})
125
+ target.instance_variable_set("@#{name}_memo_mutex", Mutex.new)
126
+ memo = target.instance_variable_get("@#{name}_memo")
127
+ mutex = target.instance_variable_get("@#{name}_memo_mutex")
128
+ lambda do |*args|
129
+ key = args.hash
130
+ mutex.synchronize do
131
+ memo[key] = yield(*args) unless memo.key?(key)
132
+ memo[key]
133
+ end
134
+ end
135
+ end
136
+
137
+ # Synchronizes access to instance variables
138
+ #
139
+ # @yield The block to be executed in a synchronized manner
140
+ # @return [Object] The result of the block
141
+ # @!macro utility_method
142
+ def _pa_synchronize(&block)
143
+ @monitor ||= Monitor.new
144
+ @monitor.synchronize(&block)
145
+ end
146
+ end
@@ -0,0 +1,4 @@
1
+ module HasPrivateAttributes
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_private_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - sebi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Support for private attributes in classes in Ruby
14
+ email:
15
+ - gore.sebyx@yahoo.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - CHANGELOG.md
23
+ - CODE_OF_CONDUCT.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lefthook.yml
28
+ - lib/has_private_attributes.rb
29
+ - lib/has_private_attributes/version.rb
30
+ - sig/has_private_attributes.rbs
31
+ homepage: https://github.com/sebyx07/has_private_attributes
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ allowed_push_host: https://rubygems.org
36
+ homepage_uri: https://github.com/sebyx07/has_private_attributes
37
+ source_code_uri: https://github.com/sebyx07/has_private_attributes
38
+ changelog_uri: https://github.com/sebyx07/has_private_attributes
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.5.11
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Support for private attributes in classes in Ruby
58
+ test_files: []