attr_with_class 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: 8724912235d91b86a21dd395658438f82aac4c66af13ff74416a518fcb396591
4
+ data.tar.gz: 2d358460c9fd4ea8fdf1c8f4afbfdd83c0b5c385a2441706836c6620a33a96f3
5
+ SHA512:
6
+ metadata.gz: 5f15930bce81aaebaea5f5f20544df5d9bf51dea92d70d5d6847a60c5cf4510e75704da1e488555500ec765517317998d8ad60ab71df9082acbdde42f30cb197
7
+ data.tar.gz: f8e85d65317f5d661638bf39b212d015e919de0c78b1dc5be1d572007733d7a2b57810305b30fe0a527489c56e22b68e94ae60009fb34b21b5b62998fed96d0c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .gem
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.1
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.10
7
+ before_install: gem install bundler -v 1.17.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in attr_with_class.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ attr_with_class (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.18.1)
10
+ rake (13.0.6)
11
+
12
+ PLATFORMS
13
+ arm64-darwin-23
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ attr_with_class!
18
+ bundler (~> 4.0.0)
19
+ minitest (~> 5.18.0)
20
+ rake (~> 13.0.0)
21
+
22
+ CHECKSUMS
23
+ attr_with_class (0.1.0)
24
+ minitest (5.18.1) sha256=ab5ee381871aaddc3a6aa2a6abcab5c4590fec9affc20947d63f312a0fe4e9cd
25
+ rake (13.0.6) sha256=5ce4bf5037b4196c24ac62834d8db1ce175470391026bd9e557d669beeb19097
26
+
27
+ BUNDLED WITH
28
+ 4.0.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bobby Pennington
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,77 @@
1
+ # AttrWithClass
2
+
3
+ Ruby metaprogramming helpers that create type-safe attribute accessors
4
+
5
+ ```ruby
6
+ class SomeClass
7
+ attr_accessor_with_class String, :some_string, :some_other_string
8
+ end
9
+ ```
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'attr_with_class'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install attr_with_class
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ class Person
31
+ attr_accessor_with_class String, :name, :email
32
+
33
+ attr_accessor_with_non_negative_integer Integer, :age
34
+
35
+ attr_accessor_with_handler :favourite_ice_cream do |val|
36
+ raise ArgumentError, "must be a valid flavour" unless ["vanilla", "chocolate"].include(val)
37
+ val
38
+ end
39
+ end
40
+
41
+ person = Person.new
42
+
43
+ person.name = "Alice" # works
44
+ person.name = 123 # raises ArgumentError: 123 is not a String
45
+
46
+ person.age = 30 # works
47
+ person.age = -1 # raises ArgumentError: -1 is not a non-negative integer
48
+
49
+ person.favourite_ice_cream = "vanilla"
50
+ person.favourite_ice_cream = "pistachio" #raises ArgumentError: must be a valid flavour
51
+ ```
52
+
53
+ ### Available Methods
54
+
55
+ `attr_writer / attr_accessor _with_handler(*attrs, &block)`
56
+
57
+ writer or accessor with custom validation/transformation block
58
+
59
+
60
+ `attr_writer / attr_accessor _with_class(klass, *attrs)`
61
+
62
+ writer or accessor that enforces class (or subclass)
63
+
64
+
65
+ `attr_writer / attr_accessor _with_non_negative_integer(*attrs)`
66
+
67
+ writer or accessor that enforces non-negative integers
68
+
69
+ ## Development
70
+
71
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
72
+
73
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
74
+
75
+ ## Contributing
76
+
77
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bpenn9/attr_with_class.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,42 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "attr_with_class/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "attr_with_class"
8
+ spec.version = AttrWithClass::VERSION
9
+ spec.authors = ["Bobby Pennington"]
10
+ spec.email = ["bobby.pennington@shopify.com"]
11
+ spec.license = "MIT"
12
+
13
+ spec.summary = %q{A couple variations of attr_accessor which support typecheking and custom setter handling}
14
+ # spec.description = %q{TODO: Write a longer description or delete this line.}
15
+ spec.homepage = "https://github.com/bpenn9/attr_with_class"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = spec.homepage
24
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
25
+ else
26
+ raise "RubyGems 2.0 or newer is required to protect against " \
27
+ "public gem pushes."
28
+ end
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.bindir = "exe"
36
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
+ spec.require_paths = ["lib"]
38
+
39
+ spec.add_development_dependency "bundler", "~> 4.0.0"
40
+ spec.add_development_dependency "rake", "~> 13.0.0"
41
+ spec.add_development_dependency "minitest", "~> 5.18.0"
42
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "attr_with_class"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/example.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "attr_with_class"
2
+
3
+ class A
4
+ attr_accessor_with_class String, :must_be_a_string
5
+ end
6
+
7
+ #################
8
+
9
+ a = A.new
10
+
11
+ a.must_be_a_string = "abcd"
12
+
13
+ # error!
14
+ # attr_with_class throws an error because 5 is not a string
15
+ a.must_be_a_string = 5
@@ -0,0 +1,3 @@
1
+ module AttrWithClass
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "attr_with_class/version"
4
+
5
+ # references (for Dev Degree)
6
+ # https://stackoverflow.com/questions/900419/how-to-understand-the-difference-between-class-eval-and-instance-eval
7
+ # https://stackoverflow.com/questions/74885066/understanding-class-eval-and-instance-eval
8
+ # https://blog.appsignal.com/2023/07/26/an-introduction-to-metaprogramming-in-ruby.html
9
+ # https://medium.com/@camfeg/dynamic-method-definition-with-rubys-define-method-b3ffbbee8197
10
+
11
+ module AttrWithClass
12
+ def self.verify_class_or_subclass(expected_class, value)
13
+ raise(ArgumentError, "#{value} is not a #{expected_class}.") unless value.class <= expected_class
14
+ end
15
+
16
+ def self.verify_non_negative_integer(value)
17
+ raise(ArgumentError, "#{value} is not a non-negative integer (must be >= 0).") \
18
+ unless value.class <= Integer && value >= 0
19
+ end
20
+ end
21
+
22
+ # pass writes to any of attr_symbols to the handler block
23
+
24
+ def attr_writer_with_handler(*attr_symbols, &handler)
25
+ attr_symbols.each do |attr_symbol|
26
+ attr_setter_symbol = :"#{attr_symbol}="
27
+ instance_attr_symbol = :"@#{attr_symbol}"
28
+
29
+ define_method(attr_setter_symbol) do |new_attr_value|
30
+ new_attr_value = handler.call(new_attr_value)
31
+ instance_variable_set(instance_attr_symbol, new_attr_value)
32
+ end
33
+ end
34
+ end
35
+
36
+ def attr_accessor_with_handler(*attr_symbols, &handler)
37
+ attr_writer_with_handler(*attr_symbols, &handler)
38
+ attr_reader(*attr_symbols)
39
+ end
40
+
41
+ # writed to any of attr_symbols are checked for class or subclass
42
+ # special case of attr_writer_with_handler / attr_accessor_with_handler
43
+
44
+ def attr_writer_with_class(expected_class, *attr_symbols)
45
+ attr_writer_with_handler(*attr_symbols) do |new_attr_value|
46
+ AttrWithClass.verify_class_or_subclass(expected_class, new_attr_value)
47
+ new_attr_value
48
+ end
49
+ end
50
+
51
+ def attr_accessor_with_class(expected_class, *attr_symbols)
52
+ attr_accessor_with_handler(*attr_symbols) do |new_attr_value|
53
+ AttrWithClass.verify_class_or_subclass(expected_class, new_attr_value)
54
+ new_attr_value
55
+ end
56
+ end
57
+
58
+ # enforces that all inputs are Integer and >= 0
59
+ # special case of attr_writer_with_handler / attr_accessor_with_handler
60
+
61
+ def attr_writer_with_non_negative_integer(*attr_symbols)
62
+ attr_writer_with_handler(*attr_symbols) do |new_attr_value|
63
+ AttrWithClass.verify_non_negative_integer(new_attr_value)
64
+ new_attr_value
65
+ end
66
+ end
67
+
68
+ def attr_accessor_with_non_negative_integer(*attr_symbols)
69
+ attr_accessor_with_handler(*attr_symbols) do |new_attr_value|
70
+ AttrWithClass.verify_non_negative_integer(new_attr_value)
71
+ new_attr_value
72
+ end
73
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attr_with_class
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bobby Pennington
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2026-01-30 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bundler
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 4.0.0
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 4.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 13.0.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 13.0.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 5.18.0
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 5.18.0
54
+ email:
55
+ - bobby.pennington@shopify.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".gitignore"
61
+ - ".ruby-version"
62
+ - ".travis.yml"
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - attr_with_class.gemspec
69
+ - bin/console
70
+ - bin/setup
71
+ - example.rb
72
+ - lib/attr_with_class.rb
73
+ - lib/attr_with_class/version.rb
74
+ homepage: https://github.com/bpenn9/attr_with_class
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ homepage_uri: https://github.com/bpenn9/attr_with_class
80
+ source_code_uri: https://github.com/bpenn9/attr_with_class
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.6.2
96
+ specification_version: 4
97
+ summary: A couple variations of attr_accessor which support typecheking and custom
98
+ setter handling
99
+ test_files: []