guide-rail 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: 64fe06eb07e4fc4e1e9865e8f7474185539e90bd2b695c102ea9771045d741b6
4
+ data.tar.gz: cb166474c8ae652c3b0ee6ce95b6deb655f8b1270b65d0fe661dc3acbf1e2905
5
+ SHA512:
6
+ metadata.gz: 7aa7500eb8a49610ac77071bc42ed51b5711801293c85f418e6106fc8ed218dab4800ed6ec1c6b0025ea7a00a2a1e780a52999b01d31c71359b7b43020332b39
7
+ data.tar.gz: d6077ef99145c833e73a3f485094ee658ab1c9c924a1ec34f46d634f5d67b3618d187f010d8d19c2bf2cdba3d5759171fd4d1551ff1546ed0c9f5f0a4ef338ab
data/.editorconfig ADDED
@@ -0,0 +1,5 @@
1
+ [*]
2
+ indent_size = 2
3
+ indent_style = space
4
+ trim_trailing_whitespace = true
5
+ insert_final_newline = true
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ require:
2
+ - standard
3
+
4
+ inherit_gem:
5
+ standard: config/base.yml
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## [0.1.0] - 2025-07-05
2
+
3
+ - Initial release
4
+
5
+ ## [0.0.0] - 2025-06-29
6
+
7
+ - concept
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ Copyright 2025 wtnabe
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # GuideRail
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/guide-rail.svg)](https://badge.fury.io/rb/guide-rail)
4
+ [![CI](https://github.com/wtnabe/guide-rail/actions/workflows/test.yml/badge.svg)](https://github.com/wtnabe/guide-rail/actions/workflows/test.yml)
5
+
6
+ **GuideRail** is a opinionated powerful factory library for Ruby that transforms various data sources into safe, immutable `Data` objects. By leveraging the robust validation capabilities of `dry-schema`, it provides a clear and declarative way to define, validate, and instantiate your data structures.
7
+
8
+ It acts as a "guide rail," ensuring that data flowing into your application (e.g., from controller params or API responses) is valid and conforms to a defined structure before being used in your domain logic or views.
9
+
10
+ ## Key Features
11
+
12
+ - **Powerful Validation**: Built on top of `dry-schema` for comprehensive data validation and coercion. The generated Data object can be guaranteed to have the expected attribute. ( If you specify required )
13
+ - **Immutable Data Objects**: Generates instances of Ruby's native `Data` class, preventing accidental state mutations.
14
+ - **Flexible Input**: Accepts various data sources, including Hashes, Structs, and ActiveModel-like objects.
15
+ - **Simple and Extendable Factory Class**: A concise and intuitive DSL for defining data factories.
16
+ - **View-Friendly Rendering**: An optional `renderable` mode provides `nil`-safe default values, perfect for views.
17
+ - **Functional-Programming-Friendly**: An optional `monadic` mode provides integration with functional programming paradigms by returning `Dry::Monads::Result` objects.
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'guide-rail'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ $ bundle install
31
+ ```
32
+
33
+ or
34
+
35
+ ```bash
36
+ $ bundle add guide-rail
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Most Simple
42
+
43
+ ```ruby
44
+ require "guide-rail"
45
+
46
+ SimpleNonNullableSchema = Dry::Schema.Params do
47
+ required(:name).filled(:string)
48
+ end
49
+
50
+ class SimpleNonNullableCreator
51
+ extend GuideRail
52
+
53
+ schema SimpleNonNullableSchema
54
+ class_name :SimpleNonNullable
55
+ end
56
+
57
+ SimpleNonNullableCreator.from(name: nil) # => #<Dry::Schema::Result{name: nil} errors={name: ["must be filled"]} path=[]>
58
+ SimpleNonNullableCreator.from(name: "John") # => #<data SimpleNonNullable name="John">
59
+ SimpleNonNullableCreator.from({}) # => #<Dry::Schema::Result{} errors={name: ["is missing"]} path=[]>
60
+ ```
61
+
62
+ ### Nullable Object with `renderable` option
63
+
64
+ ```ruby
65
+ SimpleNullableSchema = Dry::Schema.Params do
66
+ required(:name).maybe(:string)
67
+ end
68
+
69
+ class SimpleRenderableCreator
70
+ extend GuideRail
71
+
72
+ schema SimpleNullableSchema
73
+ class_name :SimpleRenderable
74
+ renderable true
75
+ end
76
+
77
+ SimpleRenderableCreator.from(name: nil) # => #<data SimpleRenderable name="">
78
+ SimpleRenderableCreator.from(name: "John") # => #<data SimpleRenderable name="John">
79
+ SimpleRenderableCreator.from({}) # => #<Dry::Schema::Result{} errors={name: ["is missing"]} path=[]>
80
+ ```
81
+
82
+ ### extendable Data class
83
+
84
+ You can give a block to Creator class, the generated Data class can be extended by the block ( applying to `define` class method ).
85
+
86
+ This can be used to define decorator methods and conversion processes, so the generated Data class can also be used as a so-called ViewModel.
87
+
88
+ ```ruby
89
+ OptionalSchema = Dry::Schema.Params do
90
+ optional(:name).filled(:string)
91
+ end
92
+
93
+ class OptionalAndYieldAccepter
94
+ extend GuideRail
95
+
96
+ class_name :ExtendedData
97
+ schema OptionalSchema
98
+ renderable true
99
+
100
+ yield_block do
101
+ alias_method :name_orig, :name
102
+ define_method :name do
103
+ "decorated #{name_orig}"
104
+ end
105
+ end
106
+ end
107
+
108
+ OptionalAndYieldAccepter.from({}).name.start_with? "decorated" # => true
109
+ ```
110
+
111
+ ### Monadic mode
112
+
113
+ ```ruby
114
+ SimpleNonNullableSchema = Dry::Schema.Params do
115
+ required(:name).filled(:string)
116
+ end
117
+
118
+ class SimpleNonNullableMonadicCreator
119
+ extend GuideRail
120
+
121
+ schema SimpleNonNullableSchema
122
+ class_name :SimpleNonNullableMonadic
123
+ monadic true
124
+ end
125
+
126
+ SimpleNonNullableMonadicCreator.from({}).either(
127
+ ->(e) { e.value! },
128
+ ->(e) { e.errors.to_h }
129
+ )
130
+ # => {name: ["is missing"]}
131
+ ```
132
+
133
+ ## Not Implemented
134
+
135
+ * i18n support
136
+ * Railtie support
137
+ * Transparent conversion of errors
138
+
139
+ ## Development
140
+
141
+ 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.
142
+
143
+ 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
144
+
145
+ ## Contributing
146
+
147
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wtnabe/guide-rail.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create(:spec) do |t|
7
+ t.libs << "spec"
8
+ t.test_globs = ["spec/**/*_spec.rb"]
9
+ end
10
+
11
+ require "standard/rake"
12
+
13
+ task default: %i[spec standard]
data/lib/guide-rail.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "guide_rail"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GuideRail
4
+ VERSION = "0.1.0"
5
+ end
data/lib/guide_rail.rb ADDED
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+ require "dry/monads"
5
+ require_relative "guide_rail/version"
6
+
7
+ Dry::Schema.load_extensions(:monads)
8
+
9
+ module GuideRail
10
+ include Dry::Monads[:result]
11
+
12
+ #
13
+ # @param [String|Symbol] klass
14
+ #
15
+ def class_name(klass)
16
+ @_class = klass
17
+ end
18
+
19
+ #
20
+ # @param [Dry::Schema::Params] schema
21
+ #
22
+ def schema(schema)
23
+ @_schema = schema
24
+ end
25
+
26
+ #
27
+ # @param [bool] bool
28
+ #
29
+ def monadic(bool)
30
+ @_monadic = bool
31
+ end
32
+
33
+ #
34
+ # @param [bool] bool
35
+ #
36
+ def renderable(bool)
37
+ @_renderable = bool
38
+ end
39
+
40
+ #
41
+ # @param [Proc] block
42
+ #
43
+ def yield_block(&block)
44
+ @_block = block
45
+ end
46
+
47
+ #
48
+ # @return [Data]
49
+ #
50
+ def create_class!
51
+ Object.const_get @_class
52
+ rescue NameError
53
+ @_attrs = @_schema.key_map.to_a.map { |k| k.name.to_sym }
54
+ klass = Data.define(*@_attrs, &@_block)
55
+ Object.const_set(@_class, klass)
56
+ klass
57
+ end
58
+
59
+ #
60
+ # @param [Hash] data
61
+ # @return [Data|Dry::Schema::Result|Dry::Monads::Result<Data|Dry::Schema::Result>]
62
+ #
63
+ def from(data)
64
+ acceptance = accept(data)
65
+ create_class!
66
+ result = @_schema.call(acceptance)
67
+ Object.const_get @_class
68
+
69
+ if result.success?
70
+ new_value = Object.const_get(@_class).new(
71
+ *(
72
+ if @_renderable
73
+ to_renderable(acceptance)
74
+ else
75
+ acceptance
76
+ end
77
+ ).values_at(*@_attrs).map(&:freeze)
78
+ )
79
+
80
+ @_monadic ? Success(new_value) : new_value
81
+ else
82
+ @_monadic ? result.to_monad : result
83
+ end
84
+ end
85
+
86
+ #
87
+ # @param [untyped] data
88
+ # @return [Hash]
89
+ #
90
+ def accept(data)
91
+ if data.respond_to? :attributes
92
+ # ActiveModel
93
+ data.attributes.transform_keys(&:to_sym)
94
+ elsif data.respond_to? :to_h
95
+ # Struct, OpenStruct, Hashie, etc...
96
+ data.to_h
97
+ elsif data.is_a? Hash
98
+ data
99
+ elsif data.respond_to? :to_hash
100
+ # Hash convertible
101
+ data.to_hash
102
+ end
103
+ end
104
+
105
+ #
106
+ # @param [Hash] original
107
+ # @param [Hash] default
108
+ # @return [Hash]
109
+ #
110
+ def to_renderable(original, default = renderable_default)
111
+ default.merge(original) { |key, d_val, o_val|
112
+ o_val.nil? ? d_val : o_val
113
+ }
114
+ end
115
+
116
+ #
117
+ # @param [Array] key_map
118
+ # @return [Hash]
119
+ #
120
+ def renderable_default(key_map = @_schema.key_map.dump)
121
+ Hash[*key_map.map { |e|
122
+ case e
123
+ when Hash
124
+ [e.keys.first.to_sym, renderable_default(e.values.first)]
125
+ when String
126
+ [e.to_sym, ""]
127
+ end
128
+ }.flatten]
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+ module GuideRail
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+
5
+ def class_name: (klass: String | Symbol) -> String | Symbol
6
+ def schema: (schema: Dry::Schema::Params) -> Dry::Schema::Params
7
+ def renderable: (bool: bool) -> bool
8
+ def yield_block: { } -> void
9
+ def create_class!: () -> Data
10
+ def from: (data: Hash) -> Data | Dry::Schema::Result | Dry::Monads::Result<Data | Dry::Schema::Result>
11
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: guide-rail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - wtnabe
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-07-06 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: dry-schema
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-monads
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'
40
+ description: GuideRail transforms various data sources into safe, immutable `Data`
41
+ objects. By leveraging the robust validation capabilities of `dry-schema`, it provides
42
+ a clear and declarative way to define, validate, and instantiate your data structures.
43
+ email:
44
+ - 18510+wtnabe@users.noreply.github.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".editorconfig"
50
+ - ".rubocop.yml"
51
+ - ".standard.yml"
52
+ - CHANGELOG.md
53
+ - LICENSE
54
+ - README.md
55
+ - Rakefile
56
+ - lib/guide-rail.rb
57
+ - lib/guide_rail.rb
58
+ - lib/guide_rail/version.rb
59
+ - sig/guide-rail.rbs
60
+ homepage: https://github.com/wtnabe/guide-rail
61
+ licenses: []
62
+ metadata:
63
+ allowed_push_host: https://rubygems.org
64
+ homepage_uri: https://github.com/wtnabe/guide-rail
65
+ source_code_uri: https://github.com/wtnabe/guide-rail
66
+ changelog_uri: https://github.com/wtnabe/guide-rail/blob/main/CHANGELOG.md
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.2.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.6.2
82
+ specification_version: 4
83
+ summary: A factory library to create safe, immutable Data objects from various sources
84
+ using dry-schema and Data ( Ruby 3.2+ ).
85
+ test_files: []