schema-model 0.7.4 → 0.8.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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +50 -9
- data/lib/schema/active_model_parsing_errors.rb +31 -0
- data/lib/schema/active_model_validations.rb +2 -16
- data/lib/schema/attribute_values.rb +35 -0
- data/lib/schema/deep_copy.rb +33 -0
- data/lib/schema/errors.rb +4 -0
- data/lib/schema/model.rb +19 -0
- data/lib/schema/parsers/american.rb +4 -12
- data/lib/schema/parsers/array.rb +1 -0
- data/lib/schema/parsers/common.rb +17 -26
- data/lib/schema/parsers/string_value.rb +23 -0
- data/lib/schema/parsing_status.rb +20 -0
- data/lib/schema/utils.rb +27 -0
- data/lib/schema/version.rb +1 -1
- data/lib/schema-model.rb +8 -0
- metadata +9 -17
- data/.github/dependabot.yml +0 -18
- data/.github/workflows/ci.yml +0 -93
- data/.github/workflows/release.yml +0 -93
- data/.gitignore +0 -51
- data/.release-please-manifest.json +0 -3
- data/.rubocop.yml +0 -68
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/ARCHITECTURE.md +0 -161
- data/CLAUDE.md +0 -87
- data/Gemfile +0 -16
- data/Gemfile.lock +0 -134
- data/release-please-config.json +0 -12
- data/schema-model.gemspec +0 -24
- data/script/console +0 -7
- data/script/coverage_badge.rb +0 -49
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schema-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Doug Youch
|
|
@@ -34,23 +34,12 @@ executables:
|
|
|
34
34
|
extensions: []
|
|
35
35
|
extra_rdoc_files: []
|
|
36
36
|
files:
|
|
37
|
-
- ".github/dependabot.yml"
|
|
38
|
-
- ".github/workflows/ci.yml"
|
|
39
|
-
- ".github/workflows/release.yml"
|
|
40
|
-
- ".gitignore"
|
|
41
|
-
- ".release-please-manifest.json"
|
|
42
|
-
- ".rubocop.yml"
|
|
43
|
-
- ".ruby-gemset"
|
|
44
|
-
- ".ruby-version"
|
|
45
|
-
- ARCHITECTURE.md
|
|
46
37
|
- CHANGELOG.md
|
|
47
|
-
- CLAUDE.md
|
|
48
|
-
- Gemfile
|
|
49
|
-
- Gemfile.lock
|
|
50
38
|
- LICENSE.txt
|
|
51
39
|
- README.md
|
|
52
40
|
- bin/schema-json2csv
|
|
53
41
|
- lib/schema-model.rb
|
|
42
|
+
- lib/schema/active_model_parsing_errors.rb
|
|
54
43
|
- lib/schema/active_model_validations.rb
|
|
55
44
|
- lib/schema/all.rb
|
|
56
45
|
- lib/schema/array_headers.rb
|
|
@@ -60,7 +49,9 @@ files:
|
|
|
60
49
|
- lib/schema/associations/has_many.rb
|
|
61
50
|
- lib/schema/associations/has_one.rb
|
|
62
51
|
- lib/schema/associations/schema_creator.rb
|
|
52
|
+
- lib/schema/attribute_values.rb
|
|
63
53
|
- lib/schema/csv_parser.rb
|
|
54
|
+
- lib/schema/deep_copy.rb
|
|
64
55
|
- lib/schema/errors.rb
|
|
65
56
|
- lib/schema/model.rb
|
|
66
57
|
- lib/schema/parsers/american.rb
|
|
@@ -68,19 +59,20 @@ files:
|
|
|
68
59
|
- lib/schema/parsers/common.rb
|
|
69
60
|
- lib/schema/parsers/hash.rb
|
|
70
61
|
- lib/schema/parsers/json.rb
|
|
62
|
+
- lib/schema/parsers/string_value.rb
|
|
71
63
|
- lib/schema/parsing_errors.rb
|
|
64
|
+
- lib/schema/parsing_status.rb
|
|
72
65
|
- lib/schema/utils.rb
|
|
73
66
|
- lib/schema/version.rb
|
|
74
67
|
- lib/schema_validator.rb
|
|
75
|
-
- release-please-config.json
|
|
76
|
-
- schema-model.gemspec
|
|
77
|
-
- script/console
|
|
78
|
-
- script/coverage_badge.rb
|
|
79
68
|
homepage: https://github.com/dougyouch/schema
|
|
80
69
|
licenses:
|
|
81
70
|
- MIT
|
|
82
71
|
metadata:
|
|
83
72
|
rubygems_mfa_required: 'true'
|
|
73
|
+
source_code_uri: https://github.com/dougyouch/schema
|
|
74
|
+
changelog_uri: https://github.com/dougyouch/schema/blob/master/CHANGELOG.md
|
|
75
|
+
bug_tracker_uri: https://github.com/dougyouch/schema/issues
|
|
84
76
|
rdoc_options: []
|
|
85
77
|
require_paths:
|
|
86
78
|
- lib
|
data/.github/dependabot.yml
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
updates:
|
|
3
|
-
# keeps the SHA-pinned actions in .github/workflows current
|
|
4
|
-
- package-ecosystem: github-actions
|
|
5
|
-
directory: /
|
|
6
|
-
schedule:
|
|
7
|
-
interval: weekly
|
|
8
|
-
commit-message:
|
|
9
|
-
prefix: ci
|
|
10
|
-
include: scope
|
|
11
|
-
|
|
12
|
-
- package-ecosystem: bundler
|
|
13
|
-
directory: /
|
|
14
|
-
schedule:
|
|
15
|
-
interval: weekly
|
|
16
|
-
commit-message:
|
|
17
|
-
prefix: build
|
|
18
|
-
include: scope
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
# release-please PRs change only these files. Their runs are triggered by
|
|
7
|
-
# github-actions[bot], which GitHub holds for approval until they expire as
|
|
8
|
-
# failures; release.yml runs this workflow before publishing anyway. A PR is
|
|
9
|
-
# skipped only if every file it changes is listed here.
|
|
10
|
-
pull_request:
|
|
11
|
-
paths-ignore:
|
|
12
|
-
- CHANGELOG.md
|
|
13
|
-
- .release-please-manifest.json
|
|
14
|
-
- lib/schema/version.rb
|
|
15
|
-
workflow_call:
|
|
16
|
-
|
|
17
|
-
permissions:
|
|
18
|
-
contents: read
|
|
19
|
-
|
|
20
|
-
concurrency:
|
|
21
|
-
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
22
|
-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
23
|
-
|
|
24
|
-
jobs:
|
|
25
|
-
lint:
|
|
26
|
-
name: RuboCop
|
|
27
|
-
runs-on: ubuntu-latest
|
|
28
|
-
steps:
|
|
29
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
30
|
-
with:
|
|
31
|
-
persist-credentials: false
|
|
32
|
-
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
|
33
|
-
with:
|
|
34
|
-
ruby-version: '4.0'
|
|
35
|
-
bundler-cache: true
|
|
36
|
-
- run: bundle exec rubocop --format github
|
|
37
|
-
|
|
38
|
-
# Runs on the Ruby in .ruby-version, the version Gemfile.lock is resolved against
|
|
39
|
-
test:
|
|
40
|
-
name: Tests
|
|
41
|
-
runs-on: ubuntu-latest
|
|
42
|
-
steps:
|
|
43
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
-
with:
|
|
45
|
-
persist-credentials: false
|
|
46
|
-
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
|
47
|
-
with:
|
|
48
|
-
# no ruby-version: setup-ruby reads .ruby-version
|
|
49
|
-
bundler-cache: true
|
|
50
|
-
- run: bundle exec rspec
|
|
51
|
-
# feeds the coverage badge
|
|
52
|
-
- name: Upload coverage report
|
|
53
|
-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
54
|
-
with:
|
|
55
|
-
name: coverage
|
|
56
|
-
path: coverage/
|
|
57
|
-
# .last_run.json (read by the badge job) is a dotfile; upload-artifact skips those by default
|
|
58
|
-
include-hidden-files: true
|
|
59
|
-
retention-days: 14
|
|
60
|
-
|
|
61
|
-
# Publishes coverage.svg and branches.svg to the orphan `badges` branch; the README badges point at them.
|
|
62
|
-
# Skipped when called from release.yml (github.workflow is the caller's name there).
|
|
63
|
-
coverage-badge:
|
|
64
|
-
if: github.workflow == 'CI' && github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
65
|
-
needs: test
|
|
66
|
-
runs-on: ubuntu-latest
|
|
67
|
-
permissions:
|
|
68
|
-
contents: write
|
|
69
|
-
steps:
|
|
70
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
71
|
-
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
|
72
|
-
with:
|
|
73
|
-
ruby-version: '4.0'
|
|
74
|
-
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
75
|
-
with:
|
|
76
|
-
name: coverage
|
|
77
|
-
path: coverage/
|
|
78
|
-
- run: |
|
|
79
|
-
ruby script/coverage_badge.rb coverage/.last_run.json "$RUNNER_TEMP/coverage.svg"
|
|
80
|
-
ruby script/coverage_badge.rb coverage/.last_run.json "$RUNNER_TEMP/branches.svg" branch
|
|
81
|
-
- name: Push badge to badges branch
|
|
82
|
-
run: |
|
|
83
|
-
git config user.name 'github-actions[bot]'
|
|
84
|
-
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
85
|
-
if git fetch --depth=1 origin badges; then
|
|
86
|
-
git switch badges
|
|
87
|
-
else
|
|
88
|
-
git switch --orphan badges
|
|
89
|
-
fi
|
|
90
|
-
cp "$RUNNER_TEMP/coverage.svg" "$RUNNER_TEMP/branches.svg" .
|
|
91
|
-
git add coverage.svg branches.svg
|
|
92
|
-
git diff --cached --quiet || git commit -m "coverage badge for ${GITHUB_SHA::7}"
|
|
93
|
-
git push origin badges
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
# release-please keeps a release PR open that bumps lib/schema/version.rb
|
|
4
|
-
# and CHANGELOG.md from conventional commits. Merging it tags the release,
|
|
5
|
-
# creates the GitHub release and publishes the gem to RubyGems.
|
|
6
|
-
# Run it manually (workflow_dispatch) to retry publishing the current version.
|
|
7
|
-
on:
|
|
8
|
-
push:
|
|
9
|
-
branches: [master]
|
|
10
|
-
workflow_dispatch:
|
|
11
|
-
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
|
|
15
|
-
concurrency:
|
|
16
|
-
group: release
|
|
17
|
-
cancel-in-progress: false
|
|
18
|
-
|
|
19
|
-
jobs:
|
|
20
|
-
release-please:
|
|
21
|
-
runs-on: ubuntu-latest
|
|
22
|
-
permissions:
|
|
23
|
-
contents: write # release commits, tags and GitHub releases
|
|
24
|
-
issues: write # release labels
|
|
25
|
-
pull-requests: write # the release PR
|
|
26
|
-
outputs:
|
|
27
|
-
release_created: ${{ steps.release.outputs.release_created }}
|
|
28
|
-
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
29
|
-
version: ${{ steps.release.outputs.version }}
|
|
30
|
-
pr_branch: ${{ steps.release.outputs.pr && fromJSON(steps.release.outputs.pr).headBranchName || '' }}
|
|
31
|
-
steps:
|
|
32
|
-
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
|
|
33
|
-
id: release
|
|
34
|
-
with:
|
|
35
|
-
config-file: release-please-config.json
|
|
36
|
-
manifest-file: .release-please-manifest.json
|
|
37
|
-
|
|
38
|
-
# The release PR is opened with GITHUB_TOKEN, which doesn't trigger CI on it,
|
|
39
|
-
# so run the suite here before anything is published.
|
|
40
|
-
ci:
|
|
41
|
-
needs: release-please
|
|
42
|
-
if: needs.release-please.outputs.release_created == 'true' || github.event_name == 'workflow_dispatch'
|
|
43
|
-
uses: ./.github/workflows/ci.yml
|
|
44
|
-
# ceiling for the called workflow: its coverage-badge job declares contents: write
|
|
45
|
-
# (skipped when called from here); lint/test still run with ci.yml's top-level contents: read
|
|
46
|
-
permissions:
|
|
47
|
-
contents: write
|
|
48
|
-
|
|
49
|
-
publish:
|
|
50
|
-
needs: [release-please, ci]
|
|
51
|
-
runs-on: ubuntu-latest
|
|
52
|
-
environment:
|
|
53
|
-
name: rubygems
|
|
54
|
-
url: https://rubygems.org/gems/schema-model
|
|
55
|
-
permissions:
|
|
56
|
-
contents: write # attach the gem to the GitHub release
|
|
57
|
-
id-token: write # RubyGems trusted publishing (OIDC)
|
|
58
|
-
steps:
|
|
59
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
60
|
-
with:
|
|
61
|
-
# the new tag on release; the dispatched commit on a manual retry
|
|
62
|
-
ref: ${{ needs.release-please.outputs.tag_name || github.sha }}
|
|
63
|
-
persist-credentials: false
|
|
64
|
-
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
|
65
|
-
with:
|
|
66
|
-
ruby-version: '4.0'
|
|
67
|
-
|
|
68
|
-
- name: Read version
|
|
69
|
-
run: |
|
|
70
|
-
version=$(ruby -e 'puts Gem::Specification.load("schema-model.gemspec").version')
|
|
71
|
-
echo "VERSION=$version" >> "$GITHUB_ENV"
|
|
72
|
-
echo "TAG=v$version" >> "$GITHUB_ENV"
|
|
73
|
-
|
|
74
|
-
- name: Build gem
|
|
75
|
-
run: gem build schema-model.gemspec
|
|
76
|
-
|
|
77
|
-
- uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
|
|
78
|
-
|
|
79
|
-
# A version that's already on RubyGems is skipped, so a manual retry can finish a
|
|
80
|
-
# partially published release.
|
|
81
|
-
- name: Push gem
|
|
82
|
-
run: |
|
|
83
|
-
if curl -sf "https://rubygems.org/api/v2/rubygems/schema-model/versions/$VERSION.json" > /dev/null; then
|
|
84
|
-
echo "schema-model $VERSION is already published"
|
|
85
|
-
else
|
|
86
|
-
gem push "schema-model-$VERSION.gem"
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
- name: Attach gem to GitHub release
|
|
90
|
-
env:
|
|
91
|
-
GH_TOKEN: ${{ github.token }}
|
|
92
|
-
run: |
|
|
93
|
-
gh release upload "$TAG" "schema-model-$VERSION.gem" --repo "$GITHUB_REPOSITORY" --clobber
|
data/.gitignore
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# rcov generated
|
|
2
|
-
coverage
|
|
3
|
-
coverage.data
|
|
4
|
-
|
|
5
|
-
# rdoc generated
|
|
6
|
-
rdoc
|
|
7
|
-
|
|
8
|
-
# yard generated
|
|
9
|
-
doc
|
|
10
|
-
.yardoc
|
|
11
|
-
|
|
12
|
-
# bundler
|
|
13
|
-
.bundle
|
|
14
|
-
|
|
15
|
-
# jeweler generated
|
|
16
|
-
pkg
|
|
17
|
-
|
|
18
|
-
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
|
19
|
-
#
|
|
20
|
-
# * Create a file at ~/.gitignore
|
|
21
|
-
# * Include files you want ignored
|
|
22
|
-
# * Run: git config --global core.excludesfile ~/.gitignore
|
|
23
|
-
#
|
|
24
|
-
# After doing this, these files will be ignored in all your git projects,
|
|
25
|
-
# saving you from having to 'pollute' every project you touch with them
|
|
26
|
-
#
|
|
27
|
-
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
|
28
|
-
#
|
|
29
|
-
# For MacOS:
|
|
30
|
-
#
|
|
31
|
-
.DS_Store
|
|
32
|
-
|
|
33
|
-
# For TextMate
|
|
34
|
-
#*.tmproj
|
|
35
|
-
#tmtags
|
|
36
|
-
|
|
37
|
-
# For emacs:
|
|
38
|
-
*~
|
|
39
|
-
\#*
|
|
40
|
-
.\#*
|
|
41
|
-
|
|
42
|
-
# For vim:
|
|
43
|
-
*.swp
|
|
44
|
-
|
|
45
|
-
# For redcar:
|
|
46
|
-
#.redcar
|
|
47
|
-
|
|
48
|
-
# For rubinius:
|
|
49
|
-
#*.rbc
|
|
50
|
-
|
|
51
|
-
*.gem
|
data/.rubocop.yml
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
AllCops:
|
|
2
|
-
TargetRubyVersion: 3.2
|
|
3
|
-
NewCops: enable
|
|
4
|
-
SuggestExtensions: false
|
|
5
|
-
Exclude:
|
|
6
|
-
- 'vendor/**/*'
|
|
7
|
-
- 'coverage/**/*'
|
|
8
|
-
|
|
9
|
-
# Relaxed metrics for existing codebase
|
|
10
|
-
Metrics/MethodLength:
|
|
11
|
-
Max: 35
|
|
12
|
-
|
|
13
|
-
Metrics/AbcSize:
|
|
14
|
-
Max: 40
|
|
15
|
-
|
|
16
|
-
Metrics/ClassLength:
|
|
17
|
-
Max: 200
|
|
18
|
-
|
|
19
|
-
Metrics/ModuleLength:
|
|
20
|
-
Max: 200
|
|
21
|
-
|
|
22
|
-
Metrics/CyclomaticComplexity:
|
|
23
|
-
Max: 15
|
|
24
|
-
|
|
25
|
-
Metrics/PerceivedComplexity:
|
|
26
|
-
Max: 15
|
|
27
|
-
|
|
28
|
-
Metrics/BlockLength:
|
|
29
|
-
Exclude:
|
|
30
|
-
- 'spec/**/*'
|
|
31
|
-
- '*.gemspec'
|
|
32
|
-
|
|
33
|
-
# Style preferences
|
|
34
|
-
Style/Documentation:
|
|
35
|
-
Enabled: false
|
|
36
|
-
|
|
37
|
-
Style/FrozenStringLiteralComment:
|
|
38
|
-
EnforcedStyle: always
|
|
39
|
-
|
|
40
|
-
Layout/LineLength:
|
|
41
|
-
Max: 130
|
|
42
|
-
Exclude:
|
|
43
|
-
- 'spec/**/*'
|
|
44
|
-
|
|
45
|
-
# File naming - allow hyphenated gem name
|
|
46
|
-
Naming/FileName:
|
|
47
|
-
Exclude:
|
|
48
|
-
- 'lib/schema-model.rb'
|
|
49
|
-
|
|
50
|
-
# DSL methods following ActiveRecord conventions (has_one, has_many)
|
|
51
|
-
Naming/PredicatePrefix:
|
|
52
|
-
Exclude:
|
|
53
|
-
- 'lib/schema/associations/has_one.rb'
|
|
54
|
-
- 'lib/schema/associations/has_many.rb'
|
|
55
|
-
- 'lib/schema/associations/schema_creator.rb'
|
|
56
|
-
|
|
57
|
-
# Dynamic eval is used for metaprogramming in this DSL gem
|
|
58
|
-
Style/DocumentDynamicEvalDefinition:
|
|
59
|
-
Enabled: false
|
|
60
|
-
|
|
61
|
-
# Short parameter names acceptable in simple setters
|
|
62
|
-
Naming/MethodParameterName:
|
|
63
|
-
AllowedNames:
|
|
64
|
-
- v
|
|
65
|
-
|
|
66
|
-
# Boolean parameters acceptable in internal methods
|
|
67
|
-
Style/OptionalBooleanParameter:
|
|
68
|
-
Enabled: false
|
data/.ruby-gemset
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
schema
|
data/.ruby-version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ruby-4.0.7
|
data/ARCHITECTURE.md
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
This document describes the internal architecture of the `schema-model` gem.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The gem transforms hash data into strongly-typed Ruby objects with parsing, validation, and nested associations. The core flow is:
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Hash Data → from_hash() → Parser Methods → Schema Instance
|
|
11
|
-
↓
|
|
12
|
-
parsing_errors (if invalid)
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Module Hierarchy
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
Schema::All (convenience bundle)
|
|
19
|
-
├── Schema::Model (core attribute system)
|
|
20
|
-
│ └── Schema::Parsers::Common
|
|
21
|
-
├── Schema::Associations::HasOne
|
|
22
|
-
├── Schema::Associations::HasMany
|
|
23
|
-
├── Schema::Parsers::American
|
|
24
|
-
├── Schema::Parsers::Array
|
|
25
|
-
├── Schema::Parsers::Hash
|
|
26
|
-
├── Schema::Parsers::Json
|
|
27
|
-
└── Schema::ActiveModelValidations
|
|
28
|
-
|
|
29
|
-
Opt-in (via schema_include):
|
|
30
|
-
├── Schema::ArrayHeaders (map CSV headers to attributes)
|
|
31
|
-
└── Schema::Arrays (models to/from flat arrays)
|
|
32
|
-
|
|
33
|
-
Standalone:
|
|
34
|
-
├── Schema::CSVParser (class; reads CSV rows into models)
|
|
35
|
-
└── SchemaValidator (ActiveModel validator for `schema: true`)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
`schema_include` both includes a module and records it in `schema_config[:schema_includes]`, so it is also included in every nested association class, including ones defined later.
|
|
39
|
-
|
|
40
|
-
## Core Components
|
|
41
|
-
|
|
42
|
-
### Schema::Model (`lib/schema/model.rb`)
|
|
43
|
-
|
|
44
|
-
The foundation module providing:
|
|
45
|
-
|
|
46
|
-
- **`attribute(name, type, options)`**: Defines schema fields. Each call:
|
|
47
|
-
1. Adds field metadata to the class's `schema` hash via `add_value_to_class_method`
|
|
48
|
-
2. Generates getter, setter, and `<name>_was_set?` methods
|
|
49
|
-
3. Setter invokes `parse_<type>` method automatically
|
|
50
|
-
|
|
51
|
-
- **`from_hash(data, skip_fields)`**: Class method that creates instance and calls `update_attributes`
|
|
52
|
-
|
|
53
|
-
- **`update_attributes(data, skip_fields)`**: Iterates the data's key/value pairs, matches them against the schema, and invokes setters. Plain attributes are set before associations, so an association's `external_type_field` can read a sibling attribute. Each key is looked up on its own: symbol keys in `schema`, anything else in `schema_with_string_keys` (rebuilt whenever `schema` changes). `skip_fields` entries match a field by name or alias, as a symbol or string; a `{ association: [...] }` entry passes a nested list down.
|
|
54
|
-
|
|
55
|
-
- **Aliases**: each alias adds a second schema entry with `alias_of:` pointing at the real attribute, plus aliased getter/setter methods. Serialization and array conversion skip entries with `alias_of`.
|
|
56
|
-
|
|
57
|
-
- **`as_json` / `to_hash`**: Serialization back to hash format
|
|
58
|
-
|
|
59
|
-
### Schema::Parsers::Common (`lib/schema/parsers/common.rb`)
|
|
60
|
-
|
|
61
|
-
Base parser methods for fundamental types:
|
|
62
|
-
- `parse_integer`, `parse_float`, `parse_string`, `parse_string_or_nil`
|
|
63
|
-
- `parse_boolean`, `parse_time`, `parse_date`
|
|
64
|
-
|
|
65
|
-
Each parser:
|
|
66
|
-
1. Accepts `(field_name, parsing_errors, value)`
|
|
67
|
-
2. Returns converted value or nil
|
|
68
|
-
3. Adds to `parsing_errors` on failure (never raises)
|
|
69
|
-
|
|
70
|
-
Additional parsers extend these: `Parsers::American` (date formats), `Parsers::Array`, `Parsers::Hash`, `Parsers::Json`.
|
|
71
|
-
|
|
72
|
-
### Schema::Associations (`lib/schema/associations/`)
|
|
73
|
-
|
|
74
|
-
**HasOne** and **HasMany** define nested relationships:
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
has_one(:profile) { attribute :bio, :string }
|
|
78
|
-
has_many(:posts) { attribute :title, :string }
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Each association gets a generated class (`SchemaHasOne<Name>` / `SchemaHasMany<Name>` by default, or `class_name:`), defined as a constant inside the parent class. `base_class:` subclasses an existing schema instead of creating a fresh model class.
|
|
82
|
-
|
|
83
|
-
Both use **SchemaCreator** (`schema_creator.rb`) to:
|
|
84
|
-
1. Determine which class to instantiate (static or dynamic)
|
|
85
|
-
2. Call `from_hash` on the nested class
|
|
86
|
-
3. Propagate parsing errors to parent: `invalid` when the nested model has parsing errors, `incompatible` when the value isn't a hash (or, for has-many, an array), and `unknown` when no dynamic type matches
|
|
87
|
-
|
|
88
|
-
`has_many ..., from: :hash` accepts a hash keyed by id instead of an array; each key is written to `hash_key_field` (default `:id`) on the nested model.
|
|
89
|
-
|
|
90
|
-
**DynamicTypes** enables polymorphic associations:
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
has_many(:items, type_field: :kind) do
|
|
94
|
-
add_type('widget') { attribute :size, :integer }
|
|
95
|
-
add_type('gadget') { attribute :power, :float }
|
|
96
|
-
default_type { } # fallback
|
|
97
|
-
end
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
The `type_field` option tells SchemaCreator which key in the nested data determines the subclass (the type field attribute's aliases are checked too); `external_type_field` reads the type from an attribute on the parent instead. `type_ignorecase: true` compares type names case-insensitively. Each `add_type` creates a subclass of the association class, defined on the parent class as `<Name>AssociationType<Type>`.
|
|
101
|
-
|
|
102
|
-
`Associations::Base` stores the parent class by name and resolves it with `Object.const_get` (so reloaded constants are picked up); a parent that was anonymous when the association was declared is kept by reference instead.
|
|
103
|
-
|
|
104
|
-
### Schema::Utils (`lib/schema/utils.rb`)
|
|
105
|
-
|
|
106
|
-
Utility methods for:
|
|
107
|
-
- `classify_name`: String → ClassName conversion
|
|
108
|
-
- `create_schema_class`: Dynamically creates nested schema classes
|
|
109
|
-
- `add_association_class`: Wires up association with proper modules
|
|
110
|
-
- `add_attribute_default_methods` / `add_association_default_methods`: Default value handling (`copy_default` hands each read a deep copy of the default)
|
|
111
|
-
|
|
112
|
-
### Error Handling
|
|
113
|
-
|
|
114
|
-
Two error storage mechanisms:
|
|
115
|
-
|
|
116
|
-
1. **Schema::Errors** (`lib/schema/errors.rb`): Simple hash-based storage, used standalone
|
|
117
|
-
|
|
118
|
-
2. **ActiveModel::Errors**: When `Schema::ActiveModelValidations` is included, `parsing_errors` returns `ActiveModel::Errors` instance
|
|
119
|
-
|
|
120
|
-
Parsing errors are distinct from validation errors:
|
|
121
|
-
- **Parsing errors**: Type conversion failures (string "abc" → integer)
|
|
122
|
-
- **Validation errors**: Business rule failures (via `validates` DSL)
|
|
123
|
-
|
|
124
|
-
`parsed!` raises `ParsingException` when there are parsing errors and `valid_model!` raises `ValidationException` when validations fail; `valid!` calls both. `valid?` runs validations only and does not look at parsing errors.
|
|
125
|
-
|
|
126
|
-
Parsing errors use these codes from `Schema::ParsingErrors`: `invalid`, `incompatible`, `unknown`, `unknown_attribute`, `unhandled_type`.
|
|
127
|
-
|
|
128
|
-
### CSV and Arrays
|
|
129
|
-
|
|
130
|
-
- **`Schema::ArrayHeaders`** (`lib/schema/array_headers.rb`): `map_headers_to_attributes(headers)` returns a nested hash of `{ field: { index: n } }` entries. Has-many fields are matched as `<prefix><n><key>` (prefix is the association name or an alias, `n` counts from 1) and map to `{ indexes: [...] }`. Has-one fields are matched by their own key or alias; a column already claimed by the parent or an earlier has-one isn't reused. Associations never map to a column of their own.
|
|
131
|
-
- **`Schema::Arrays`** (`lib/schema/arrays.rb`): `to_headers`, `to_empty_array`, `to_a` and `from_array(array, mapped_headers)`. Has-many associations need a `size:` option for the fixed-width methods.
|
|
132
|
-
- **`Schema::CSVParser`** (`lib/schema/csv_parser.rb`): wraps a `CSV` object, maps its header row once, and yields a model per row (`each`, `shift`, `missing_fields`).
|
|
133
|
-
|
|
134
|
-
### Inheritance Helper Integration
|
|
135
|
-
|
|
136
|
-
The gem uses `inheritance-helper` for schema inheritance. Key method:
|
|
137
|
-
- `add_value_to_class_method(:schema, name => options)`: Accumulates schema definitions across class hierarchy
|
|
138
|
-
|
|
139
|
-
This allows schema classes to inherit attributes from parent classes.
|
|
140
|
-
|
|
141
|
-
## Data Flow Example
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
class OrderSchema
|
|
145
|
-
include Schema::All
|
|
146
|
-
attribute :total, :float
|
|
147
|
-
has_one(:customer) { attribute :name, :string }
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
order = OrderSchema.from_hash({
|
|
151
|
-
total: "99.50",
|
|
152
|
-
customer: { name: "Alice" }
|
|
153
|
-
})
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
1. `from_hash` calls `new.update_attributes(data)`
|
|
157
|
-
2. `update_attributes` iterates keys, finds `:total` in schema
|
|
158
|
-
3. Calls `self.total = "99.50"` → invokes `parse_float`
|
|
159
|
-
4. For `:customer`, recognizes association, delegates to SchemaCreator
|
|
160
|
-
5. SchemaCreator calls `OrderSchema::SchemaHasOneCustomer.from_hash({name: "Alice"})`
|
|
161
|
-
6. Returns populated `OrderSchema` instance
|
data/CLAUDE.md
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Build and Test Commands
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Install dependencies
|
|
9
|
-
bundle install
|
|
10
|
-
|
|
11
|
-
# Run all tests
|
|
12
|
-
bundle exec rspec
|
|
13
|
-
|
|
14
|
-
# Run a single test file
|
|
15
|
-
bundle exec rspec spec/schema/model_spec.rb
|
|
16
|
-
|
|
17
|
-
# Run a specific test by line number
|
|
18
|
-
bundle exec rspec spec/schema/model_spec.rb:42
|
|
19
|
-
|
|
20
|
-
# Run linter
|
|
21
|
-
bundle exec rubocop
|
|
22
|
-
|
|
23
|
-
# Auto-fix linter issues
|
|
24
|
-
bundle exec rubocop -A
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Architecture
|
|
28
|
-
|
|
29
|
-
This is a Ruby gem (`schema-model`) for data transformation, validation, and type safety. It transforms hash data into strongly-typed model objects.
|
|
30
|
-
|
|
31
|
-
### Core Module Structure
|
|
32
|
-
|
|
33
|
-
- **Schema::Model** (`lib/schema/model.rb`) - Foundation module providing `attribute` definitions, `from_hash` class method, and attribute accessors.
|
|
34
|
-
|
|
35
|
-
- **Schema::All** (`lib/schema/all.rb`) - Convenience module bundling Model + Associations + Parsers + ActiveModel validations. This is the typical include.
|
|
36
|
-
|
|
37
|
-
- **Schema::Parsers** - Type parsers in `lib/schema/parsers/`:
|
|
38
|
-
- `Common` - integer, string, string_or_nil, float, time, date, boolean (included by `Schema::Model`)
|
|
39
|
-
- `American` - american_date, american_time (MM/DD/YYYY format)
|
|
40
|
-
- `Array` - array with optional separator and data_type
|
|
41
|
-
- `Hash` - hash/dictionary values
|
|
42
|
-
- `Json` - JSON string parsing
|
|
43
|
-
|
|
44
|
-
- **Schema::Associations** - `HasOne` and `HasMany` for nested relationships. `DynamicTypes` enables polymorphic associations via `type_field`/`add_type`.
|
|
45
|
-
|
|
46
|
-
- **Schema::Arrays** (`lib/schema/arrays.rb`) - Convert models to/from flat arrays for CSV support.
|
|
47
|
-
|
|
48
|
-
- **Schema::ArrayHeaders** (`lib/schema/array_headers.rb`) - Map CSV headers to schema attributes.
|
|
49
|
-
|
|
50
|
-
- **Schema::CSVParser** (`lib/schema/csv_parser.rb`) - Class that reads `CSV` rows into models (needs ArrayHeaders + Arrays on the model).
|
|
51
|
-
|
|
52
|
-
- **SchemaValidator** (`lib/schema_validator.rb`) - ActiveModel validator behind `validates :assoc, schema: true`.
|
|
53
|
-
|
|
54
|
-
### Key Patterns
|
|
55
|
-
|
|
56
|
-
**Attribute Definition**: Each `attribute` call generates getter, setter, and `<name>_was_set?` predicate. Setter invokes type-specific parser.
|
|
57
|
-
|
|
58
|
-
**Parsing Errors**: Stored in `parsing_errors`. Parsers add errors for invalid values rather than raising exceptions. With ActiveModelValidations, use `parsed?`/`parsed!`; `valid?` does not check parsing errors.
|
|
59
|
-
|
|
60
|
-
**Schema Inheritance**: Uses `inheritance-helper` gem. Schema definitions accumulate via `add_value_to_class_method(:schema, ...)`.
|
|
61
|
-
|
|
62
|
-
**Dynamic Types**: For polymorphic associations, use `type_field` option with `add_type` blocks. Supports `external_type_field`, `type_ignorecase`, and `default_type`.
|
|
63
|
-
|
|
64
|
-
## CI
|
|
65
|
-
|
|
66
|
-
`.github/workflows/ci.yml` runs RuboCop and the specs on the `.ruby-version` Ruby. On pushes to `master`, it publishes `coverage.svg` (line) and `branches.svg` (branch) from `script/coverage_badge.rb` to the orphan `badges` branch for the README badges.
|
|
67
|
-
|
|
68
|
-
## Releases
|
|
69
|
-
|
|
70
|
-
Releases are automated by release-please (`.github/workflows/release.yml`). Conventional commits on `master` (`fix:` → patch, `feat:` → minor, `!`/`BREAKING CHANGE` → major) update an open release PR that bumps `lib/schema/version.rb` (`Schema::VERSION`, read by the gemspec) and `CHANGELOG.md`. Merging that PR tags `vX.Y.Z`, creates the GitHub release, and publishes the gem to RubyGems. Don't bump the version by hand.
|
|
71
|
-
|
|
72
|
-
## Code Commits
|
|
73
|
-
|
|
74
|
-
Format using angular formatting:
|
|
75
|
-
```
|
|
76
|
-
<type>(<scope>): <short summary>
|
|
77
|
-
```
|
|
78
|
-
- **type**: build|ci|docs|feat|fix|perf|refactor|test
|
|
79
|
-
- **scope**: The feature or component of the service we're working on
|
|
80
|
-
- **summary**: Summary in present tense. Not capitalized. No period at the end.
|
|
81
|
-
|
|
82
|
-
## Documentation Maintenance
|
|
83
|
-
|
|
84
|
-
When modifying the codebase, keep documentation in sync:
|
|
85
|
-
- **ARCHITECTURE.md** - Update when adding/removing classes, changing component relationships, or altering data flow patterns
|
|
86
|
-
- **README.md** - Update when adding new features, changing public APIs, or modifying usage examples
|
|
87
|
-
- **CHANGELOG.md** - Generated by release-please from commit messages; don't edit by hand except to fix release notes
|
data/Gemfile
DELETED