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.
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.7.4
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
@@ -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
@@ -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
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.7.4"
3
- }
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
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- gem 'inheritance-helper'
6
-
7
- group :development do
8
- gem 'activemodel'
9
- gem 'csv'
10
- gem 'rubocop'
11
- end
12
-
13
- group :spec do
14
- gem 'rspec'
15
- gem 'simplecov'
16
- end