schema-model 0.6.11 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0604e96e9d70ef3b47b7581ef0c6c43005960af46dde776565700abf86482ad5
4
- data.tar.gz: cdfdbf20d77cfd81521df44308083b1134062c19e041752a2a194ee05c420c4d
3
+ metadata.gz: 274769a5b3652ece9a8c2583a8867310882800377563f7e5e8eb387a219ea4a3
4
+ data.tar.gz: f42a01e476eeb0101df7e5fbaa8bb9fde390637016876ed749a1f1f850d0ae0e
5
5
  SHA512:
6
- metadata.gz: 590a840b90865bc31c4c75c0f34f10135c9d410532c6585d64afa20e39f712262bb43794cea9414877eb8df3602567cf057deb75867d9436eea0315a313aea15
7
- data.tar.gz: 0d9b8ee1ba799df269f396c68692e9210f90bde87192f3f121809a42db99647630b12c5624ed108ff58079a661a1f51b6547e876797dc32a0a887ebb7bcbfc41
6
+ metadata.gz: cef6b96d50da9c84ac9b80afd6c74f6f941668eecea8d7d6dd5260e3e73b0163f7906deb86af4f7bca141efbdccd3fffa2d12d6955fa7986ccc889c89c472194
7
+ data.tar.gz: 4372f3250e704cc8569411659663fc946ab3f87b2ea7a6255aca7966dde8dbcff8e2ef8eec50ece7aa3bba5f4ebc01ea352219f79c743db32974663f807e5440
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ lint:
11
+ name: RuboCop
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: '3.4'
20
+ bundler-cache: true
21
+
22
+ - name: Run RuboCop
23
+ run: bundle exec rubocop --format github
24
+
25
+ test:
26
+ name: Tests (Ruby ${{ matrix.ruby }})
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ ruby: ['3.2', '3.3', '3.4']
32
+
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+
36
+ - name: Set up Ruby ${{ matrix.ruby }}
37
+ uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: ${{ matrix.ruby }}
40
+ bundler-cache: true
41
+
42
+ - name: Run tests
43
+ run: bundle exec rspec
44
+
45
+ - name: Upload coverage to Codecov
46
+ if: matrix.ruby == '3.4'
47
+ uses: codecov/codecov-action@v4
48
+ with:
49
+ files: coverage/coverage.xml
50
+ fail_ci_if_error: false
51
+ verbose: true
52
+ env:
53
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
data/.rubocop.yml CHANGED
@@ -1,11 +1,73 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.2
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+ Exclude:
6
+ - 'bin/**/*'
7
+ - 'vendor/**/*'
8
+ - 'coverage/**/*'
9
+
10
+ # Relaxed metrics for existing codebase
11
+ Metrics/MethodLength:
12
+ Max: 35
13
+
14
+ Metrics/AbcSize:
15
+ Max: 40
16
+
17
+ Metrics/ClassLength:
18
+ Max: 200
19
+
20
+ Metrics/ModuleLength:
21
+ Max: 200
22
+
23
+ Metrics/CyclomaticComplexity:
24
+ Max: 15
25
+
26
+ Metrics/PerceivedComplexity:
27
+ Max: 15
28
+
29
+ Metrics/BlockLength:
30
+ Exclude:
31
+ - 'spec/**/*'
32
+ - '*.gemspec'
33
+
34
+ # Style preferences
35
+ Style/Documentation:
36
+ Enabled: false
37
+
38
+ Style/FrozenStringLiteralComment:
39
+ EnforcedStyle: always
40
+
41
+ Layout/LineLength:
42
+ Max: 130
43
+ Exclude:
44
+ - 'spec/**/*'
45
+
46
+ # File naming - allow hyphenated gem name
1
47
  Naming/FileName:
48
+ Exclude:
49
+ - 'lib/schema-model.rb'
50
+
51
+ # Gemspec settings
52
+ Gemspec/RequiredRubyVersion:
2
53
  Enabled: false
3
- Metrics/LineLength:
4
- Max: 120
5
- Metrics/ModuleLength:
6
- Max: 120
7
- Metrics/MethodLength:
8
- Max: 20
9
- AllCops:
54
+
55
+ # DSL methods following ActiveRecord conventions (has_one, has_many)
56
+ Naming/PredicatePrefix:
10
57
  Exclude:
11
- - 'spec/**/*_spec.rb'
58
+ - 'lib/schema/associations/has_one.rb'
59
+ - 'lib/schema/associations/has_many.rb'
60
+ - 'lib/schema/associations/schema_creator.rb'
61
+
62
+ # Dynamic eval is used for metaprogramming in this DSL gem
63
+ Style/DocumentDynamicEvalDefinition:
64
+ Enabled: false
65
+
66
+ # Short parameter names acceptable in simple setters
67
+ Naming/MethodParameterName:
68
+ AllowedNames:
69
+ - v
70
+
71
+ # Boolean parameters acceptable in internal methods
72
+ Style/OptionalBooleanParameter:
73
+ Enabled: false
data/ARCHITECTURE.md ADDED
@@ -0,0 +1,135 @@
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::Associations::HasOne
21
+ ├── Schema::Associations::HasMany
22
+ ├── Schema::Parsers::Common
23
+ ├── Schema::Parsers::American
24
+ ├── Schema::Parsers::Array
25
+ ├── Schema::Parsers::Hash
26
+ ├── Schema::Parsers::Json
27
+ └── Schema::ActiveModelValidations
28
+ ```
29
+
30
+ ## Core Components
31
+
32
+ ### Schema::Model (`lib/schema/model.rb`)
33
+
34
+ The foundation module providing:
35
+
36
+ - **`attribute(name, type, options)`**: Defines schema fields. Each call:
37
+ 1. Adds field metadata to the class's `schema` hash via `add_value_to_class_method`
38
+ 2. Generates getter, setter, and `<name>_was_set?` methods
39
+ 3. Setter invokes `parse_<type>` method automatically
40
+
41
+ - **`from_hash(data, skip_fields)`**: Class method that creates instance and calls `update_attributes`
42
+
43
+ - **`update_attributes(data, skip_fields)`**: Iterates hash keys, matches against schema, invokes setters
44
+
45
+ - **`as_json` / `to_hash`**: Serialization back to hash format
46
+
47
+ ### Schema::Parsers::Common (`lib/schema/parsers/common.rb`)
48
+
49
+ Base parser methods for fundamental types:
50
+ - `parse_integer`, `parse_float`, `parse_string`, `parse_string_or_nil`
51
+ - `parse_boolean`, `parse_time`, `parse_date`
52
+
53
+ Each parser:
54
+ 1. Accepts `(field_name, parsing_errors, value)`
55
+ 2. Returns converted value or nil
56
+ 3. Adds to `parsing_errors` on failure (never raises)
57
+
58
+ Additional parsers extend these: `Parsers::American` (date formats), `Parsers::Array`, `Parsers::Hash`, `Parsers::Json`.
59
+
60
+ ### Schema::Associations (`lib/schema/associations/`)
61
+
62
+ **HasOne** and **HasMany** define nested relationships:
63
+
64
+ ```ruby
65
+ has_one(:profile) { attribute :bio, :string }
66
+ has_many(:posts) { attribute :title, :string }
67
+ ```
68
+
69
+ Both use **SchemaCreator** (`schema_creator.rb`) to:
70
+ 1. Determine which class to instantiate (static or dynamic)
71
+ 2. Call `from_hash` on the nested class
72
+ 3. Propagate parsing errors to parent
73
+
74
+ **DynamicTypes** enables polymorphic associations:
75
+
76
+ ```ruby
77
+ has_many(:items, type_field: :kind) do
78
+ add_type('widget') { attribute :size, :integer }
79
+ add_type('gadget') { attribute :power, :float }
80
+ default_type { } # fallback
81
+ end
82
+ ```
83
+
84
+ The `type_field` option tells SchemaCreator which data key determines the subclass.
85
+
86
+ ### Schema::Utils (`lib/schema/utils.rb`)
87
+
88
+ Utility methods for:
89
+ - `classify_name`: String → ClassName conversion
90
+ - `create_schema_class`: Dynamically creates nested schema classes
91
+ - `add_association_class`: Wires up association with proper modules
92
+ - `add_attribute_default_methods` / `add_association_default_methods`: Default value handling
93
+
94
+ ### Error Handling
95
+
96
+ Two error storage mechanisms:
97
+
98
+ 1. **Schema::Errors** (`lib/schema/errors.rb`): Simple hash-based storage, used standalone
99
+
100
+ 2. **ActiveModel::Errors**: When `Schema::ActiveModelValidations` is included, `parsing_errors` returns `ActiveModel::Errors` instance
101
+
102
+ Parsing errors are distinct from validation errors:
103
+ - **Parsing errors**: Type conversion failures (string "abc" → integer)
104
+ - **Validation errors**: Business rule failures (via `validates` DSL)
105
+
106
+ Methods `parsed!` and `valid!` raise `ParsingException`/`ValidationException` respectively.
107
+
108
+ ### Inheritance Helper Integration
109
+
110
+ The gem uses `inheritance-helper` for schema inheritance. Key method:
111
+ - `add_value_to_class_method(:schema, name => options)`: Accumulates schema definitions across class hierarchy
112
+
113
+ This allows schema classes to inherit attributes from parent classes.
114
+
115
+ ## Data Flow Example
116
+
117
+ ```ruby
118
+ class OrderSchema
119
+ include Schema::All
120
+ attribute :total, :float
121
+ has_one(:customer) { attribute :name, :string }
122
+ end
123
+
124
+ order = OrderSchema.from_hash({
125
+ total: "99.50",
126
+ customer: { name: "Alice" }
127
+ })
128
+ ```
129
+
130
+ 1. `from_hash` calls `new.update_attributes(data)`
131
+ 2. `update_attributes` iterates keys, finds `:total` in schema
132
+ 3. Calls `self.total = "99.50"` → invokes `parse_float`
133
+ 4. For `:customer`, recognizes association, delegates to SchemaCreator
134
+ 5. SchemaCreator calls `OrderSchema::SchemaHasOneCustomer.from_hash({name: "Alice"})`
135
+ 6. Returns populated `OrderSchema` instance
data/CLAUDE.md ADDED
@@ -0,0 +1,74 @@
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, float, time, date, boolean
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
+ ### Key Patterns
51
+
52
+ **Attribute Definition**: Each `attribute` call generates getter, setter, and `<name>_was_set?` predicate. Setter invokes type-specific parser.
53
+
54
+ **Parsing Errors**: Stored in `parsing_errors`. Parsers add errors for invalid values rather than raising exceptions. With ActiveModelValidations, use `parsed?`/`parsed!`.
55
+
56
+ **Schema Inheritance**: Uses `inheritance-helper` gem. Schema definitions accumulate via `add_value_to_class_method(:schema, ...)`.
57
+
58
+ **Dynamic Types**: For polymorphic associations, use `type_field` option with `add_type` blocks. Supports `external_type_field`, `type_ignorecase`, and `default_type`.
59
+
60
+ ## Code Commits
61
+
62
+ Format using angular formatting:
63
+ ```
64
+ <type>(<scope>): <short summary>
65
+ ```
66
+ - **type**: build|ci|docs|feat|fix|perf|refactor|test
67
+ - **scope**: The feature or component of the service we're working on
68
+ - **summary**: Summary in present tense. Not capitalized. No period at the end.
69
+
70
+ ## Documentation Maintenance
71
+
72
+ When modifying the codebase, keep documentation in sync:
73
+ - **ARCHITECTURE.md** - Update when adding/removing classes, changing component relationships, or altering data flow patterns
74
+ - **README.md** - Update when adding new features, changing public APIs, or modifying usage examples
data/Gemfile CHANGED
@@ -7,11 +7,11 @@ gem 'inheritance-helper'
7
7
  group :development do
8
8
  gem 'activemodel'
9
9
  gem 'csv'
10
- gem 'rake'
11
10
  gem 'rubocop'
12
11
  end
13
12
 
14
13
  group :spec do
15
14
  gem 'rspec'
16
15
  gem 'simplecov'
16
+ gem 'simplecov-cobertura'
17
17
  end
data/Gemfile.lock CHANGED
@@ -1,62 +1,62 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activemodel (8.0.2)
5
- activesupport (= 8.0.2)
6
- activesupport (8.0.2)
4
+ activemodel (8.1.2)
5
+ activesupport (= 8.1.2)
6
+ activesupport (8.1.2)
7
7
  base64
8
- benchmark (>= 0.3)
9
8
  bigdecimal
10
9
  concurrent-ruby (~> 1.0, >= 1.3.1)
11
10
  connection_pool (>= 2.2.5)
12
11
  drb
13
12
  i18n (>= 1.6, < 2)
13
+ json
14
14
  logger (>= 1.4.2)
15
15
  minitest (>= 5.1)
16
16
  securerandom (>= 0.3)
17
17
  tzinfo (~> 2.0, >= 2.0.5)
18
18
  uri (>= 0.13.1)
19
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)
20
+ base64 (0.3.0)
21
+ bigdecimal (4.0.1)
22
+ concurrent-ruby (1.3.6)
23
+ connection_pool (3.0.2)
24
+ csv (3.3.5)
25
+ diff-lcs (1.6.2)
27
26
  docile (1.4.1)
28
- drb (2.2.1)
29
- i18n (1.14.7)
27
+ drb (2.2.3)
28
+ i18n (1.14.8)
30
29
  concurrent-ruby (~> 1.0)
31
30
  inheritance-helper (0.2.5)
32
- json (2.10.2)
33
- language_server-protocol (3.17.0.4)
31
+ json (2.18.0)
32
+ language_server-protocol (3.17.0.5)
34
33
  lint_roller (1.1.0)
35
34
  logger (1.7.0)
36
- minitest (5.25.5)
37
- parallel (1.26.3)
38
- parser (3.3.7.4)
35
+ minitest (6.0.1)
36
+ prism (~> 1.5)
37
+ parallel (1.27.0)
38
+ parser (3.3.10.1)
39
39
  ast (~> 2.4.1)
40
40
  racc
41
- prism (1.4.0)
41
+ prism (1.9.0)
42
42
  racc (1.8.1)
43
43
  rainbow (3.1.1)
44
- rake (13.2.1)
45
- regexp_parser (2.10.0)
46
- rspec (3.13.0)
44
+ regexp_parser (2.11.3)
45
+ rexml (3.4.4)
46
+ rspec (3.13.2)
47
47
  rspec-core (~> 3.13.0)
48
48
  rspec-expectations (~> 3.13.0)
49
49
  rspec-mocks (~> 3.13.0)
50
- rspec-core (3.13.3)
50
+ rspec-core (3.13.6)
51
51
  rspec-support (~> 3.13.0)
52
- rspec-expectations (3.13.3)
52
+ rspec-expectations (3.13.5)
53
53
  diff-lcs (>= 1.2.0, < 2.0)
54
54
  rspec-support (~> 3.13.0)
55
- rspec-mocks (3.13.2)
55
+ rspec-mocks (3.13.7)
56
56
  diff-lcs (>= 1.2.0, < 2.0)
57
57
  rspec-support (~> 3.13.0)
58
- rspec-support (3.13.2)
59
- rubocop (1.75.2)
58
+ rspec-support (3.13.7)
59
+ rubocop (1.84.0)
60
60
  json (~> 2.3)
61
61
  language_server-protocol (~> 3.17.0.2)
62
62
  lint_roller (~> 1.1.0)
@@ -64,26 +64,29 @@ GEM
64
64
  parser (>= 3.3.0.2)
65
65
  rainbow (>= 2.2.2, < 4.0)
66
66
  regexp_parser (>= 2.9.3, < 3.0)
67
- rubocop-ast (>= 1.44.0, < 2.0)
67
+ rubocop-ast (>= 1.49.0, < 2.0)
68
68
  ruby-progressbar (~> 1.7)
69
69
  unicode-display_width (>= 2.4.0, < 4.0)
70
- rubocop-ast (1.44.0)
70
+ rubocop-ast (1.49.0)
71
71
  parser (>= 3.3.7.2)
72
- prism (~> 1.4)
72
+ prism (~> 1.7)
73
73
  ruby-progressbar (1.13.0)
74
74
  securerandom (0.4.1)
75
75
  simplecov (0.22.0)
76
76
  docile (~> 1.1)
77
77
  simplecov-html (~> 0.11)
78
78
  simplecov_json_formatter (~> 0.1)
79
- simplecov-html (0.13.1)
79
+ simplecov-cobertura (3.1.0)
80
+ rexml
81
+ simplecov (~> 0.19)
82
+ simplecov-html (0.13.2)
80
83
  simplecov_json_formatter (0.1.4)
81
84
  tzinfo (2.0.6)
82
85
  concurrent-ruby (~> 1.0)
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)
86
+ unicode-display_width (3.2.0)
87
+ unicode-emoji (~> 4.1)
88
+ unicode-emoji (4.2.0)
89
+ uri (1.1.1)
87
90
 
88
91
  PLATFORMS
89
92
  arm64-darwin-24
@@ -93,10 +96,10 @@ DEPENDENCIES
93
96
  activemodel
94
97
  csv
95
98
  inheritance-helper
96
- rake
97
99
  rspec
98
100
  rubocop
99
101
  simplecov
102
+ simplecov-cobertura
100
103
 
101
104
  BUNDLED WITH
102
105
  2.6.2