schema-model 0.6.10 → 0.6.11
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 +4 -4
- data/.cursor-rules +39 -0
- data/.ruby-version +1 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +71 -42
- data/README.md +152 -125
- data/docs/ARCHITECTURE.md +171 -0
- data/lib/schema/associations/has_many.rb +4 -4
- data/lib/schema/associations/has_one.rb +2 -2
- data/lib/schema/associations/schema_creator.rb +5 -5
- data/lib/schema/model.rb +11 -8
- data/schema-model.gemspec +1 -1
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0604e96e9d70ef3b47b7581ef0c6c43005960af46dde776565700abf86482ad5
|
4
|
+
data.tar.gz: cdfdbf20d77cfd81521df44308083b1134062c19e041752a2a194ee05c420c4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 590a840b90865bc31c4c75c0f34f10135c9d410532c6585d64afa20e39f712262bb43794cea9414877eb8df3602567cf057deb75867d9436eea0315a313aea15
|
7
|
+
data.tar.gz: 0d9b8ee1ba799df269f396c68692e9210f90bde87192f3f121809a42db99647630b12c5624ed108ff58079a661a1f51b6547e876797dc32a0a887ebb7bcbfc41
|
data/.cursor-rules
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Cursor Rules Configuration
|
2
|
+
|
3
|
+
# Ignore common development and system files
|
4
|
+
ignore {
|
5
|
+
".git/"
|
6
|
+
".DS_Store"
|
7
|
+
"*.log"
|
8
|
+
"tmp/"
|
9
|
+
"node_modules/"
|
10
|
+
"coverage/"
|
11
|
+
}
|
12
|
+
|
13
|
+
# Define common code block markers
|
14
|
+
markers {
|
15
|
+
start "# START"
|
16
|
+
end "# END"
|
17
|
+
}
|
18
|
+
|
19
|
+
# Define language-specific comment styles
|
20
|
+
comments {
|
21
|
+
ruby "#"
|
22
|
+
javascript "//"
|
23
|
+
python "#"
|
24
|
+
html "<!--"
|
25
|
+
css "/*"
|
26
|
+
}
|
27
|
+
|
28
|
+
# Define safe edit boundaries
|
29
|
+
boundaries {
|
30
|
+
function_start "^(def|function|class) "
|
31
|
+
block_start "^(if|while|for|begin|module) "
|
32
|
+
}
|
33
|
+
|
34
|
+
# Define merge conflict markers to avoid
|
35
|
+
conflicts {
|
36
|
+
start "<<<<<<< "
|
37
|
+
middle "======="
|
38
|
+
end ">>>>>>> "
|
39
|
+
}
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.2
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,68 +1,97 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activemodel (
|
5
|
-
activesupport (=
|
6
|
-
activesupport (
|
7
|
-
|
4
|
+
activemodel (8.0.2)
|
5
|
+
activesupport (= 8.0.2)
|
6
|
+
activesupport (8.0.2)
|
7
|
+
base64
|
8
|
+
benchmark (>= 0.3)
|
9
|
+
bigdecimal
|
10
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
11
|
+
connection_pool (>= 2.2.5)
|
12
|
+
drb
|
8
13
|
i18n (>= 1.6, < 2)
|
14
|
+
logger (>= 1.4.2)
|
9
15
|
minitest (>= 5.1)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
securerandom (>= 0.3)
|
17
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
18
|
+
uri (>= 0.13.1)
|
19
|
+
ast (2.4.3)
|
20
|
+
base64 (0.2.0)
|
21
|
+
benchmark (0.4.0)
|
22
|
+
bigdecimal (3.1.9)
|
23
|
+
concurrent-ruby (1.3.5)
|
24
|
+
connection_pool (2.5.0)
|
25
|
+
csv (3.3.3)
|
26
|
+
diff-lcs (1.6.1)
|
27
|
+
docile (1.4.1)
|
28
|
+
drb (2.2.1)
|
29
|
+
i18n (1.14.7)
|
16
30
|
concurrent-ruby (~> 1.0)
|
17
31
|
inheritance-helper (0.2.5)
|
18
|
-
|
19
|
-
|
20
|
-
|
32
|
+
json (2.10.2)
|
33
|
+
language_server-protocol (3.17.0.4)
|
34
|
+
lint_roller (1.1.0)
|
35
|
+
logger (1.7.0)
|
36
|
+
minitest (5.25.5)
|
37
|
+
parallel (1.26.3)
|
38
|
+
parser (3.3.7.4)
|
21
39
|
ast (~> 2.4.1)
|
40
|
+
racc
|
41
|
+
prism (1.4.0)
|
42
|
+
racc (1.8.1)
|
22
43
|
rainbow (3.1.1)
|
23
|
-
rake (13.
|
24
|
-
regexp_parser (2.
|
25
|
-
|
26
|
-
|
27
|
-
rspec-
|
28
|
-
rspec-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
rspec-expectations (3.11.0)
|
44
|
+
rake (13.2.1)
|
45
|
+
regexp_parser (2.10.0)
|
46
|
+
rspec (3.13.0)
|
47
|
+
rspec-core (~> 3.13.0)
|
48
|
+
rspec-expectations (~> 3.13.0)
|
49
|
+
rspec-mocks (~> 3.13.0)
|
50
|
+
rspec-core (3.13.3)
|
51
|
+
rspec-support (~> 3.13.0)
|
52
|
+
rspec-expectations (3.13.3)
|
33
53
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
-
rspec-support (~> 3.
|
35
|
-
rspec-mocks (3.
|
54
|
+
rspec-support (~> 3.13.0)
|
55
|
+
rspec-mocks (3.13.2)
|
36
56
|
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
-
rspec-support (~> 3.
|
38
|
-
rspec-support (3.
|
39
|
-
rubocop (1.
|
57
|
+
rspec-support (~> 3.13.0)
|
58
|
+
rspec-support (3.13.2)
|
59
|
+
rubocop (1.75.2)
|
60
|
+
json (~> 2.3)
|
61
|
+
language_server-protocol (~> 3.17.0.2)
|
62
|
+
lint_roller (~> 1.1.0)
|
40
63
|
parallel (~> 1.10)
|
41
|
-
parser (>= 3.
|
64
|
+
parser (>= 3.3.0.2)
|
42
65
|
rainbow (>= 2.2.2, < 4.0)
|
43
|
-
regexp_parser (>=
|
44
|
-
|
45
|
-
rubocop-ast (>= 1.16.0, < 2.0)
|
66
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
67
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
46
68
|
ruby-progressbar (~> 1.7)
|
47
|
-
unicode-display_width (>=
|
48
|
-
rubocop-ast (1.
|
49
|
-
parser (>= 3.
|
50
|
-
|
51
|
-
|
69
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
70
|
+
rubocop-ast (1.44.0)
|
71
|
+
parser (>= 3.3.7.2)
|
72
|
+
prism (~> 1.4)
|
73
|
+
ruby-progressbar (1.13.0)
|
74
|
+
securerandom (0.4.1)
|
75
|
+
simplecov (0.22.0)
|
52
76
|
docile (~> 1.1)
|
53
77
|
simplecov-html (~> 0.11)
|
54
78
|
simplecov_json_formatter (~> 0.1)
|
55
|
-
simplecov-html (0.
|
79
|
+
simplecov-html (0.13.1)
|
56
80
|
simplecov_json_formatter (0.1.4)
|
57
|
-
tzinfo (2.0.
|
81
|
+
tzinfo (2.0.6)
|
58
82
|
concurrent-ruby (~> 1.0)
|
59
|
-
unicode-display_width (
|
83
|
+
unicode-display_width (3.1.4)
|
84
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
85
|
+
unicode-emoji (4.0.4)
|
86
|
+
uri (1.0.3)
|
60
87
|
|
61
88
|
PLATFORMS
|
62
|
-
|
89
|
+
arm64-darwin-24
|
90
|
+
ruby
|
63
91
|
|
64
92
|
DEPENDENCIES
|
65
93
|
activemodel
|
94
|
+
csv
|
66
95
|
inheritance-helper
|
67
96
|
rake
|
68
97
|
rspec
|
@@ -70,4 +99,4 @@ DEPENDENCIES
|
|
70
99
|
simplecov
|
71
100
|
|
72
101
|
BUNDLED WITH
|
73
|
-
2.
|
102
|
+
2.6.2
|
data/README.md
CHANGED
@@ -1,163 +1,190 @@
|
|
1
|
-
#
|
1
|
+
# Schema
|
2
2
|
|
3
|
-
|
3
|
+
A powerful Ruby gem for data transformation, validation, and type safety. Schema provides a flexible and intuitive way to define data models with support for complex nested structures, dynamic associations, and robust validation.
|
4
4
|
|
5
5
|
[](https://travis-ci.org/dougyouch/schema)
|
6
6
|
[](https://codeclimate.com/github/dougyouch/schema/maintainability)
|
7
7
|
[](https://codeclimate.com/github/dougyouch/schema/test_coverage)
|
8
8
|
|
9
|
-
|
9
|
+
## Features
|
10
10
|
|
11
|
-
|
11
|
+
- **Type Safety**: Strong typing with automatic parsing and validation
|
12
|
+
- **Flexible Attributes**: Support for aliases and custom data types
|
13
|
+
- **Nested Models**: Complex data structures with nested associations
|
14
|
+
- **Dynamic Associations**: Runtime type-based model creation
|
15
|
+
- **ActiveModel Integration**: Seamless integration with ActiveModel validations
|
16
|
+
- **Error Handling**: Comprehensive error collection and reporting
|
17
|
+
- **CSV Support**: Built-in CSV parsing capabilities
|
12
18
|
|
13
|
-
|
19
|
+
## Installation
|
14
20
|
|
15
|
-
|
21
|
+
Add this line to your application's Gemfile:
|
16
22
|
|
17
|
-
###### spec/examples/company_schema.rb
|
18
23
|
```ruby
|
19
|
-
|
24
|
+
gem 'schema-model'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
$ bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
## Quick Start
|
34
|
+
|
35
|
+
Here's a simple example to get you started:
|
20
36
|
|
21
|
-
|
22
|
-
class
|
23
|
-
# includes model, associations, parsers and active model validations
|
37
|
+
```ruby
|
38
|
+
class UserSchema
|
24
39
|
include Schema::All
|
25
40
|
|
26
|
-
|
27
|
-
|
28
|
-
attribute :
|
29
|
-
attribute :
|
41
|
+
attribute :name, :string
|
42
|
+
attribute :age, :integer
|
43
|
+
attribute :email, :string
|
44
|
+
attribute :tags, :array, separator: ',', data_type: :string
|
45
|
+
|
46
|
+
validates :name, presence: true
|
47
|
+
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Usage
|
51
|
+
user_data = {
|
52
|
+
name: 'John Doe',
|
53
|
+
age: '30',
|
54
|
+
email: 'john@example.com',
|
55
|
+
tags: 'ruby,rails,developer'
|
56
|
+
}
|
57
|
+
|
58
|
+
user = UserSchema.from_hash(user_data)
|
59
|
+
if user.valid?
|
60
|
+
puts "User created: #{user.name}"
|
61
|
+
else
|
62
|
+
puts "Validation errors: #{user.errors.full_messages}"
|
63
|
+
end
|
64
|
+
```
|
30
65
|
|
31
|
-
|
32
|
-
# basically take a list of comma separated numbers and create an array of integers
|
33
|
-
# code snippet: str.split(',').map { |v| parse_integer(field_name, parsing_errors, v) }
|
34
|
-
attribute :number_list, :array, separator: ',', data_type: :integer
|
66
|
+
## Core Concepts
|
35
67
|
|
36
|
-
|
37
|
-
industry_schema = has_one(:industry, external_type_field: :industry_type) do
|
38
|
-
attribute :name, :string
|
68
|
+
### Attributes
|
39
69
|
|
40
|
-
|
70
|
+
Attributes define the structure of your data model. Each attribute has:
|
71
|
+
- A name
|
72
|
+
- A type
|
73
|
+
- Optional aliases
|
74
|
+
- Custom parsing rules
|
41
75
|
|
42
|
-
|
43
|
-
|
44
|
-
|
76
|
+
```ruby
|
77
|
+
attribute :name, :string, alias: 'FullName'
|
78
|
+
attribute :age, :integer
|
79
|
+
attribute :tags, :array, separator: ',', data_type: :string
|
80
|
+
```
|
45
81
|
|
46
|
-
|
47
|
-
attribute :number_of_locations, :integer
|
82
|
+
### Associations
|
48
83
|
|
49
|
-
|
50
|
-
validates :number_of_locations, presence: true
|
51
|
-
end
|
52
|
-
end
|
84
|
+
Schema supports various types of associations:
|
53
85
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
86
|
+
1. **Has One**: Single nested model
|
87
|
+
2. **Has Many**: Multiple nested models
|
88
|
+
3. **Dynamic Associations**: Type-based model creation
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
has_one(:profile) do
|
92
|
+
attribute :bio, :string
|
93
|
+
attribute :website, :string
|
94
|
+
end
|
95
|
+
|
96
|
+
has_many(:posts) do
|
97
|
+
attribute :title, :string
|
98
|
+
attribute :content, :string
|
99
|
+
end
|
100
|
+
```
|
61
101
|
|
62
|
-
|
63
|
-
attribute :main_floor, :integer
|
102
|
+
### Dynamic Types
|
64
103
|
|
65
|
-
|
66
|
-
validates :main_floor, presence: true
|
67
|
-
end
|
104
|
+
Create different model structures based on a type field:
|
68
105
|
|
69
|
-
|
70
|
-
|
106
|
+
```ruby
|
107
|
+
has_many(:vehicles, type_field: :type) do
|
108
|
+
attribute :type, :string
|
109
|
+
attribute :color, :string
|
71
110
|
|
72
|
-
|
73
|
-
|
74
|
-
end
|
111
|
+
add_type('car') do
|
112
|
+
attribute :doors, :integer
|
75
113
|
end
|
76
114
|
|
77
|
-
|
78
|
-
|
79
|
-
attribute :type, :integer
|
80
|
-
attribute :name, :string
|
81
|
-
attribute :start_date, :date
|
82
|
-
add_type(1) do # worker
|
83
|
-
attribute :manager_name, :string
|
84
|
-
end
|
85
|
-
add_type(2) do # manager
|
86
|
-
attribute :rank, :float
|
87
|
-
end
|
88
|
-
# if no or an invalid type is specified, create a default employee schema object
|
89
|
-
# useful for communicating errors in an API
|
90
|
-
default_type
|
91
|
-
|
92
|
-
# dynamic_type_names returns all the types used, except for :default
|
93
|
-
validates :type, inclusion: { in: dynamic_type_names }
|
115
|
+
add_type('truck') do
|
116
|
+
attribute :bed_length, :float
|
94
117
|
end
|
118
|
+
end
|
119
|
+
```
|
95
120
|
|
96
|
-
|
97
|
-
attribute :username, :string
|
98
|
-
attribute :email, :string
|
99
|
-
attribute :name, :string
|
121
|
+
## Advanced Features
|
100
122
|
|
101
|
-
|
102
|
-
|
123
|
+
### Custom Parsers
|
124
|
+
|
125
|
+
Define custom parsing logic for your attributes:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
attribute :custom_field, :custom_type do
|
129
|
+
def parse_custom_type(field_name, errors, value)
|
130
|
+
# Custom parsing logic
|
103
131
|
end
|
132
|
+
end
|
133
|
+
```
|
104
134
|
|
105
|
-
|
106
|
-
validates :industry_type, inclusion: { in: industry_schema.dynamic_type_names }
|
135
|
+
### CSV Integration
|
107
136
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
137
|
+
Parse CSV data directly into your models:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class UserCSVSchema
|
141
|
+
include Schema::CSVParser
|
142
|
+
|
143
|
+
attribute :name, :string
|
144
|
+
attribute :email, :string
|
113
145
|
end
|
146
|
+
|
147
|
+
users = UserCSVSchema.parse_csv(csv_data)
|
114
148
|
```
|
115
149
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
"main_floor": 5
|
130
|
-
},
|
131
|
-
{
|
132
|
-
"type": "store_front",
|
133
|
-
"address": "1st Ave",
|
134
|
-
"zip": "02211",
|
135
|
-
"main_entrance": "side door"
|
136
|
-
}
|
137
|
-
],
|
138
|
-
"employees": [
|
139
|
-
{
|
140
|
-
"type": 2,
|
141
|
-
"name": "Queen Bee",
|
142
|
-
"start_date": "2016-01-09",
|
143
|
-
"rank": "0.9"
|
144
|
-
},
|
145
|
-
{
|
146
|
-
"type": 1,
|
147
|
-
"name": "Worker Bee",
|
148
|
-
"start_date": "2018-05-10",
|
149
|
-
"manager_name": "Queen Bee"
|
150
|
-
}
|
151
|
-
],
|
152
|
-
"admins": {
|
153
|
-
"captain": {
|
154
|
-
"email": "captain@example.com",
|
155
|
-
"name": "Captain Kurk"
|
156
|
-
},
|
157
|
-
"joe": {
|
158
|
-
"email": "joe.smith@example.com",
|
159
|
-
"name": "Joe Smith"
|
160
|
-
}
|
161
|
-
}
|
150
|
+
## Using `skip_fields` to Protect Internal Fields
|
151
|
+
|
152
|
+
When instantiating a schema with `from_hash`, you can use the `skip_fields` argument to prevent certain fields (such as `id`, `created_at`, `updated_at`) from being set by user input. This is especially useful for fields managed by the database or internal logic, ensuring end users cannot override these values.
|
153
|
+
|
154
|
+
**Example:**
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
user_data = {
|
158
|
+
id: 123, # Should be managed by DB
|
159
|
+
name: 'John Doe',
|
160
|
+
email: 'john@example.com',
|
161
|
+
created_at: '2024-06-01T12:00:00Z', # Should be managed by DB
|
162
|
+
updated_at: '2024-06-01T12:00:00Z' # Should be managed by DB
|
162
163
|
}
|
164
|
+
|
165
|
+
# Skip DB-managed fields
|
166
|
+
user = UserSchema.from_hash(user_data, [:id, :created_at, :updated_at])
|
167
|
+
|
168
|
+
puts user.id # => nil (not set from user input)
|
169
|
+
puts user.created_at # => nil (not set from user input)
|
170
|
+
puts user.updated_at # => nil (not set from user input)
|
171
|
+
puts user.name # => 'John Doe'
|
163
172
|
```
|
173
|
+
|
174
|
+
**Benefit:**
|
175
|
+
- Prevents end users from setting or changing internal DB values.
|
176
|
+
- Ensures only safe, intended fields are settable from external input.
|
177
|
+
- Helps maintain data integrity and security in your application.
|
178
|
+
|
179
|
+
## Contributing
|
180
|
+
|
181
|
+
1. Fork the repository
|
182
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
183
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
184
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
185
|
+
5. Create a new Pull Request
|
186
|
+
|
187
|
+
## License
|
188
|
+
|
189
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
190
|
+
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# Schema Architecture Documentation
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Schema is a Ruby gem that provides a robust framework for data transformation, validation, and type safety. It follows a modular architecture that allows for flexible and extensible data modeling.
|
6
|
+
|
7
|
+
## Core Components
|
8
|
+
|
9
|
+
### 1. Model System (`lib/schema/model.rb`)
|
10
|
+
|
11
|
+
The foundation of the Schema system, providing:
|
12
|
+
- Attribute definition and management
|
13
|
+
- Type system integration
|
14
|
+
- Data parsing and validation
|
15
|
+
- Association handling
|
16
|
+
|
17
|
+
Key features:
|
18
|
+
- Dynamic attribute registration
|
19
|
+
- Type coercion and validation
|
20
|
+
- Nested model support
|
21
|
+
- Error collection and reporting
|
22
|
+
|
23
|
+
### 2. Association System (`lib/schema/associations/`)
|
24
|
+
|
25
|
+
Handles relationships between models:
|
26
|
+
- `has_one`: Single nested model
|
27
|
+
- `has_many`: Multiple nested models
|
28
|
+
- Dynamic associations based on type fields
|
29
|
+
- Hash-based associations
|
30
|
+
|
31
|
+
### 3. Parser System (`lib/schema/parsers/`)
|
32
|
+
|
33
|
+
Responsible for data type conversion and validation:
|
34
|
+
- Built-in parsers for common types (string, integer, float, etc.)
|
35
|
+
- Custom parser support
|
36
|
+
- Array parsing with separators
|
37
|
+
- CSV data parsing
|
38
|
+
|
39
|
+
### 4. Validation System (`lib/schema/active_model_validations.rb`)
|
40
|
+
|
41
|
+
Integrates with ActiveModel validations to provide:
|
42
|
+
- Attribute presence validation
|
43
|
+
- Format validation
|
44
|
+
- Custom validation rules
|
45
|
+
- Nested model validation
|
46
|
+
|
47
|
+
### 5. Error Handling (`lib/schema/errors.rb`, `lib/schema/parsing_errors.rb`)
|
48
|
+
|
49
|
+
Comprehensive error management:
|
50
|
+
- Parsing errors
|
51
|
+
- Validation errors
|
52
|
+
- Nested model errors
|
53
|
+
- Error message formatting
|
54
|
+
|
55
|
+
## Data Flow
|
56
|
+
|
57
|
+
1. **Initialization**
|
58
|
+
- Schema class definition
|
59
|
+
- Attribute registration
|
60
|
+
- Association setup
|
61
|
+
- Validation rules configuration
|
62
|
+
|
63
|
+
2. **Data Processing**
|
64
|
+
- Input data parsing
|
65
|
+
- Type coercion
|
66
|
+
- Association resolution
|
67
|
+
- Validation execution
|
68
|
+
|
69
|
+
3. **Error Collection**
|
70
|
+
- Parsing error collection
|
71
|
+
- Validation error aggregation
|
72
|
+
- Nested model error propagation
|
73
|
+
|
74
|
+
## Extension Points
|
75
|
+
|
76
|
+
### Custom Parsers
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
module Schema::Parsers
|
80
|
+
class CustomParser
|
81
|
+
def self.parse(field_name, errors, value)
|
82
|
+
# Custom parsing logic
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Custom Validators
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class CustomValidator < ActiveModel::Validator
|
92
|
+
def validate(record)
|
93
|
+
# Custom validation logic
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
### Custom Associations
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
module Schema::Associations
|
102
|
+
class CustomAssociation < Base
|
103
|
+
def initialize(name, options = {}, &block)
|
104
|
+
# Custom association logic
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
## Performance Considerations
|
111
|
+
|
112
|
+
1. **Attribute Access**
|
113
|
+
- Dynamic method generation for attribute accessors
|
114
|
+
- Caching of parsed values
|
115
|
+
- Lazy loading of associations
|
116
|
+
|
117
|
+
2. **Validation**
|
118
|
+
- Early termination on first error
|
119
|
+
- Parallel validation of independent attributes
|
120
|
+
- Caching of validation results
|
121
|
+
|
122
|
+
3. **Memory Management**
|
123
|
+
- Efficient error object creation
|
124
|
+
- Minimal object allocation during parsing
|
125
|
+
- Garbage collection optimization
|
126
|
+
|
127
|
+
## Security Considerations
|
128
|
+
|
129
|
+
1. **Data Validation**
|
130
|
+
- Strict type checking
|
131
|
+
- Input sanitization
|
132
|
+
- Size limits on collections
|
133
|
+
|
134
|
+
2. **Error Handling**
|
135
|
+
- Safe error message generation
|
136
|
+
- No sensitive data exposure
|
137
|
+
- Controlled error propagation
|
138
|
+
|
139
|
+
## Testing Strategy
|
140
|
+
|
141
|
+
1. **Unit Tests**
|
142
|
+
- Individual component testing
|
143
|
+
- Mock dependencies
|
144
|
+
- Edge case coverage
|
145
|
+
|
146
|
+
2. **Integration Tests**
|
147
|
+
- Component interaction testing
|
148
|
+
- Real-world usage scenarios
|
149
|
+
- Performance benchmarks
|
150
|
+
|
151
|
+
3. **Regression Tests**
|
152
|
+
- Backward compatibility
|
153
|
+
- Bug fixes verification
|
154
|
+
- Performance regression detection
|
155
|
+
|
156
|
+
## Future Considerations
|
157
|
+
|
158
|
+
1. **Planned Features**
|
159
|
+
- JSON Schema integration
|
160
|
+
- GraphQL type generation
|
161
|
+
- Async validation support
|
162
|
+
|
163
|
+
2. **Performance Improvements**
|
164
|
+
- Parallel processing
|
165
|
+
- Memory optimization
|
166
|
+
- Caching strategies
|
167
|
+
|
168
|
+
3. **API Evolution**
|
169
|
+
- Backward compatibility
|
170
|
+
- Deprecation strategy
|
171
|
+
- Version management
|
@@ -24,13 +24,13 @@ module Schema
|
|
24
24
|
@#{name}_schema_creator ||= ::Schema::Associations::SchemaCreator.new(self, #{name.inspect})
|
25
25
|
end
|
26
26
|
|
27
|
-
def #{options[:setter]}(v)
|
28
|
-
#{options[:instance_variable]} = #{name}_schema_creator.create_schemas(self, v)
|
27
|
+
def #{options[:setter]}(v, skip_fields = [])
|
28
|
+
#{options[:instance_variable]} = #{name}_schema_creator.create_schemas(self, v, skip_fields)
|
29
29
|
end
|
30
30
|
|
31
|
-
def append_to_#{options[:getter]}(v)
|
31
|
+
def append_to_#{options[:getter]}(v, skip_fields = [])
|
32
32
|
#{options[:instance_variable]} ||= []
|
33
|
-
#{options[:instance_variable]} << #{name}_schema_creator.create_schema(self, v)
|
33
|
+
#{options[:instance_variable]} << #{name}_schema_creator.create_schema(self, v, nil, skip_fields)
|
34
34
|
end
|
35
35
|
STR
|
36
36
|
)
|
@@ -24,8 +24,8 @@ module Schema
|
|
24
24
|
@#{name}_schema_creator ||= ::Schema::Associations::SchemaCreator.new(self, #{name.inspect})
|
25
25
|
end
|
26
26
|
|
27
|
-
def #{options[:setter]}(v)
|
28
|
-
#{options[:instance_variable]} = #{name}_schema_creator.create_schema(self, v)
|
27
|
+
def #{options[:setter]}(v, skip_fields = [])
|
28
|
+
#{options[:instance_variable]} = #{name}_schema_creator.create_schema(self, v, nil, skip_fields)
|
29
29
|
end
|
30
30
|
STR
|
31
31
|
)
|
@@ -17,13 +17,13 @@ module Schema
|
|
17
17
|
configure_dynamic_schema_options(options)
|
18
18
|
end
|
19
19
|
|
20
|
-
def create_schema(base_schema, data, error_name = nil)
|
20
|
+
def create_schema(base_schema, data, error_name = nil, skip_fields = [])
|
21
21
|
if data.is_a?(Hash)
|
22
22
|
unless (schema_class = get_schema_class(base_schema, data))
|
23
23
|
add_parsing_error(base_schema, error_name, UNKNOWN) if base_schema.class.capture_unknown_attributes?
|
24
24
|
return nil
|
25
25
|
end
|
26
|
-
schema = schema_class.from_hash(data)
|
26
|
+
schema = schema_class.from_hash(data, skip_fields)
|
27
27
|
add_parsing_error(base_schema, error_name, INVALID) unless schema.parsing_errors.empty?
|
28
28
|
schema
|
29
29
|
elsif !data.nil?
|
@@ -32,12 +32,12 @@ module Schema
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def create_schemas(base_schema, list)
|
35
|
+
def create_schemas(base_schema, list, skip_fields = [])
|
36
36
|
if is_list? && list.is_a?(Array)
|
37
|
-
list.each_with_index.map { |data, idx| create_schema(base_schema, data, "#{@schema_name}:#{idx}") }
|
37
|
+
list.each_with_index.map { |data, idx| create_schema(base_schema, data, "#{@schema_name}:#{idx}", skip_fields) }
|
38
38
|
elsif !is_list? && list.is_a?(Hash)
|
39
39
|
list.map do |(key, data)|
|
40
|
-
schema = create_schema(base_schema, data, "#{@schema_name}:#{key}")
|
40
|
+
schema = create_schema(base_schema, data, "#{@schema_name}:#{key}", skip_fields)
|
41
41
|
schema.send(schema.class.schema[@hash_key_field][:setter], key)
|
42
42
|
schema
|
43
43
|
end
|
data/lib/schema/model.rb
CHANGED
@@ -73,8 +73,8 @@ module Schema
|
|
73
73
|
add_aliases(name, options)
|
74
74
|
end
|
75
75
|
|
76
|
-
def from_hash(data)
|
77
|
-
new.update_attributes(data)
|
76
|
+
def from_hash(data = nil, skip_fields = [])
|
77
|
+
new.update_attributes(data, skip_fields)
|
78
78
|
end
|
79
79
|
|
80
80
|
def schema_include(mod)
|
@@ -114,10 +114,10 @@ STR
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
-
def update_attributes(data)
|
117
|
+
def update_attributes(data = nil, skip_fields = [])
|
118
118
|
schema = get_schema(data)
|
119
|
-
update_model_attributes(schema, data)
|
120
|
-
update_associations(schema, data)
|
119
|
+
update_model_attributes(schema, data, skip_fields)
|
120
|
+
update_associations(schema, data, skip_fields)
|
121
121
|
self
|
122
122
|
end
|
123
123
|
|
@@ -164,7 +164,7 @@ STR
|
|
164
164
|
self.class.schema_with_string_keys
|
165
165
|
end
|
166
166
|
|
167
|
-
def update_model_attributes(schema, data)
|
167
|
+
def update_model_attributes(schema, data, skip_fields)
|
168
168
|
data.each do |key, value|
|
169
169
|
unless schema.key?(key)
|
170
170
|
parsing_errors.add(key, ::Schema::ParsingErrors::UNKNOWN_ATTRIBUTE) if self.class.capture_unknown_attributes?
|
@@ -172,17 +172,20 @@ STR
|
|
172
172
|
end
|
173
173
|
|
174
174
|
next if schema[key][:association]
|
175
|
+
next if skip_fields.include?(key)
|
175
176
|
|
176
177
|
public_send(schema[key][:setter], value)
|
177
178
|
end
|
178
179
|
end
|
179
180
|
|
180
|
-
def update_associations(schema, data)
|
181
|
+
def update_associations(schema, data, skip_fields)
|
181
182
|
data.each do |key, value|
|
182
183
|
next unless schema.key?(key)
|
183
184
|
next unless schema[key][:association]
|
184
185
|
|
185
|
-
|
186
|
+
association_skip_fields = skip_fields.detect { |f| f.is_a?(Hash) && f.include?(key) }
|
187
|
+
association_skip_fields = association_skip_fields ? association_skip_fields[key] : []
|
188
|
+
public_send(schema[key][:setter], value, association_skip_fields)
|
186
189
|
end
|
187
190
|
end
|
188
191
|
end
|
data/schema-model.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Doug Youch
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-21 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: inheritance-helper
|
@@ -31,6 +30,7 @@ executables:
|
|
31
30
|
extensions: []
|
32
31
|
extra_rdoc_files: []
|
33
32
|
files:
|
33
|
+
- ".cursor-rules"
|
34
34
|
- ".gitignore"
|
35
35
|
- ".rubocop.yml"
|
36
36
|
- ".ruby-gemset"
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- README.md
|
43
43
|
- Rakefile
|
44
44
|
- bin/schema-json2csv
|
45
|
+
- docs/ARCHITECTURE.md
|
45
46
|
- lib/schema-model.rb
|
46
47
|
- lib/schema/active_model_validations.rb
|
47
48
|
- lib/schema/all.rb
|
@@ -71,7 +72,6 @@ homepage: https://github.com/dougyouch/schema
|
|
71
72
|
licenses:
|
72
73
|
- MIT
|
73
74
|
metadata: {}
|
74
|
-
post_install_message:
|
75
75
|
rdoc_options: []
|
76
76
|
require_paths:
|
77
77
|
- lib
|
@@ -86,8 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
86
|
- !ruby/object:Gem::Version
|
87
87
|
version: '0'
|
88
88
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
90
|
-
signing_key:
|
89
|
+
rubygems_version: 3.6.2
|
91
90
|
specification_version: 4
|
92
91
|
summary: Schema Model
|
93
92
|
test_files: []
|