rails-fields 0.3.2 → 0.3.3
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/README.md +5 -3
- data/lib/rails/fields.rb +3 -0
- data/lib/rails_fields/class_methods.rb +12 -4
- data/lib/rails_fields/railtie.rb +14 -2
- data/lib/rails_fields/utils/helpers.rb +29 -15
- data/lib/rails_fields/utils/mappings.rb +27 -24
- data/lib/rails_fields/version.rb +1 -1
- metadata +7 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72386aef5bd4ef9136597eb8226759ce716deb2d4a899b27837031c3d4678596
|
4
|
+
data.tar.gz: b0a8d07705dfba50be8270da36854db49cedc9d2c10f09b292cc48674f059965
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5b4d8dd33a0f8b4103c649901e7f7c789aea02a64411998db0f5a722c811a59b1107a1e86b4c73f9d201173376789c76b6b5664ef46a96ecacca08994efcf22
|
7
|
+
data.tar.gz: 59ed93e40c507e75a139f2321c011101070e200d5d217e4eb1752cf085e60983b614f60511fad534b64c8eb4e8dca58d6d2930c24d3e2b844d928d366c8570fc
|
data/README.md
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
<a href="https://rails-fields.dev" target="_blank"><img src="./assets/logo.svg" width="300" /></a>
|
3
3
|
</p>
|
4
4
|
|
5
|
-
[](https://badge.fury.io/rb/rails-fields)
|
5
|
+
[](https://badge.fury.io/rb/rails-fields)
|
6
6
|
|
7
7
|
# Rails Fields
|
8
8
|
|
9
9
|
Enforce field types and attributes for ActiveRecord models in Ruby on Rails applications.
|
10
10
|
|
11
|
-
- 🚀
|
11
|
+
- 🚀 Automatic ActiveRecord **Migrations** generation
|
12
12
|
- 🦄 Automatic [GraphQL types](https://graphql-ruby.org/type_definitions/objects.html) generation
|
13
13
|
- 📝 Explicit **declarative** model attributes annotation
|
14
14
|
- 💪🏻 Enforcement of fields declaration with real db columns
|
@@ -83,4 +83,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
83
83
|
|
84
84
|
## Author
|
85
85
|
|
86
|
-
Gaston Morixe - gaston@gastonmorixe.com
|
86
|
+
Gaston Morixe 2023 - gaston@gastonmorixe.com
|
87
|
+
|
88
|
+
[rails-fields.dev](https://rails-fields.dev/?gh)
|
data/lib/rails/fields.rb
ADDED
@@ -1,4 +1,7 @@
|
|
1
1
|
module RailsFields
|
2
|
+
# Lightweight, typed container for field declarations
|
3
|
+
# Include :options to maintain compatibility with code that reads it
|
4
|
+
DeclaredField = Struct.new(:name, :type, :null, :index, :options, keyword_init: true)
|
2
5
|
module ClassMethods
|
3
6
|
# TODO: Check all models at rails init app? like migrations?
|
4
7
|
|
@@ -36,16 +39,20 @@ module RailsFields
|
|
36
39
|
")
|
37
40
|
end
|
38
41
|
|
39
|
-
declared_fields <<
|
42
|
+
declared_fields << DeclaredField.new(name: name.to_s, type:, null:, index:)
|
40
43
|
end
|
41
44
|
|
42
45
|
def gql_type
|
46
|
+
unless defined?(GraphQL)
|
47
|
+
raise "GraphQL is not available. Add `gem 'graphql'` and install it, or avoid calling `gql_type`."
|
48
|
+
end
|
43
49
|
return RailsFields.processed_classes[self] if RailsFields.processed_classes[self].present?
|
44
50
|
|
45
51
|
fields = declared_fields
|
46
52
|
owner_self = self
|
47
53
|
|
48
|
-
|
54
|
+
base_object = defined?(::Types::BaseObject) ? ::Types::BaseObject : ::GraphQL::Schema::Object
|
55
|
+
type = Class.new(base_object) do
|
49
56
|
# graphql_name "#{owner_self.name}Type"
|
50
57
|
graphql_name "#{owner_self.name}"
|
51
58
|
description "A type representing a #{owner_self.name}"
|
@@ -55,8 +62,9 @@ module RailsFields
|
|
55
62
|
|
56
63
|
# Assuming a proper mapping from your custom types to GraphQL types
|
57
64
|
# TODO: use a better method or block
|
58
|
-
|
59
|
-
|
65
|
+
field_name = f.name.to_s
|
66
|
+
field_gql_type = field_name == 'id' ? GraphQL::Types::ID : Utils::RAILS_TO_GQL_TYPE_MAP[f.type]
|
67
|
+
field field_name, field_gql_type
|
60
68
|
end
|
61
69
|
end
|
62
70
|
|
data/lib/rails_fields/railtie.rb
CHANGED
@@ -14,7 +14,19 @@ module RailsFields
|
|
14
14
|
end
|
15
15
|
|
16
16
|
initializer "rails_fields.middleware" do |app|
|
17
|
-
|
17
|
+
# In Rails 8, ActiveRecord::Migration::CheckPending was removed.
|
18
|
+
# Try to insert after it when present; otherwise, append the middleware.
|
19
|
+
if defined?(Rails::VERSION) && Rails::VERSION::MAJOR >= 8
|
20
|
+
# Rails 8 removed ActiveRecord::Migration::CheckPending from the stack
|
21
|
+
app.middleware.use RailsFields::EnforceFieldsMiddleware
|
22
|
+
else
|
23
|
+
begin
|
24
|
+
app.middleware.insert_after ActiveRecord::Migration::CheckPending, RailsFields::EnforceFieldsMiddleware
|
25
|
+
rescue StandardError
|
26
|
+
# Fallback for environments where insert_after target is unavailable
|
27
|
+
app.middleware.use RailsFields::EnforceFieldsMiddleware
|
28
|
+
end
|
29
|
+
end
|
18
30
|
end
|
19
31
|
end
|
20
|
-
end
|
32
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module RailsFields
|
2
2
|
module Utils
|
3
3
|
class << self
|
4
|
+
# Minimal wrapper to represent associations in changes
|
5
|
+
AssociationRef = Struct.new(:name)
|
4
6
|
def allowed_types
|
5
7
|
# TODO: this may depend on the current database adapter or mapper
|
6
8
|
ActiveRecord::Base.connection.native_database_types.keys
|
@@ -35,7 +37,11 @@ module RailsFields
|
|
35
37
|
# @param model [ActiveRecord::Base] the model to check
|
36
38
|
# @return [Hash, Nil] the changes detected
|
37
39
|
def detect_changes(model)
|
38
|
-
|
40
|
+
# Exclude the primary key (e.g., :id) from comparisons
|
41
|
+
primary_key = model.primary_key&.to_sym
|
42
|
+
previous_fields = model.attribute_types
|
43
|
+
.to_h { |k, v| [k.to_sym, v.type] }
|
44
|
+
.reject { |name, _| name == primary_key }
|
39
45
|
declared_fields = model.declared_fields.to_h do |f|
|
40
46
|
[f.name.to_sym, {
|
41
47
|
name: f.type.to_sym,
|
@@ -88,8 +94,11 @@ module RailsFields
|
|
88
94
|
# Detect potential renames
|
89
95
|
potential_renames = []
|
90
96
|
model_changes[:removed].each do |removed_field|
|
91
|
-
#
|
92
|
-
added_field = model_changes[:added].find
|
97
|
+
# Match by type name (normalize Hash/Scalar)
|
98
|
+
added_field = model_changes[:added].find do |f|
|
99
|
+
added_type = f[:type].is_a?(Hash) ? f[:type][:name] : f[:type]
|
100
|
+
added_type == removed_field[:type]
|
101
|
+
end
|
93
102
|
if added_field
|
94
103
|
potential_renames << { from: removed_field[:name],
|
95
104
|
to: added_field[:name] }
|
@@ -102,8 +111,8 @@ module RailsFields
|
|
102
111
|
|
103
112
|
# Filter out incorrect renames (one-to-one mapping)
|
104
113
|
potential_renames.each do |rename|
|
105
|
-
next unless model_changes[:added].count { |f| f[:
|
106
|
-
model_changes[:removed].count { |f| f[:
|
114
|
+
next unless model_changes[:added].count { |f| f[:name] == rename[:to].to_sym } == 1 &&
|
115
|
+
model_changes[:removed].count { |f| f[:name] == rename[:from].to_sym } == 1
|
107
116
|
|
108
117
|
model_changes[:renamed] << rename
|
109
118
|
model_changes[:added].reject! { |f| f[:name] == rename[:to].to_sym }
|
@@ -116,15 +125,20 @@ module RailsFields
|
|
116
125
|
end
|
117
126
|
|
118
127
|
declared_foreign_keys = declared_associations.map(&:foreign_key).map(&:to_sym)
|
119
|
-
existing_foreign_keys = ActiveRecord::Base.connection
|
128
|
+
existing_foreign_keys = ActiveRecord::Base.connection
|
129
|
+
.foreign_keys(model.table_name)
|
130
|
+
.map { |fk| fk.respond_to?(:column) ? fk.column.to_sym : fk.options[:column].to_sym }
|
120
131
|
|
121
132
|
associations_added = declared_associations.select do |reflection|
|
122
133
|
!existing_foreign_keys.include?(reflection.foreign_key.to_sym)
|
123
134
|
end
|
124
135
|
|
125
|
-
associations_removed = existing_foreign_keys
|
126
|
-
!declared_foreign_keys.include?(foreign_key)
|
127
|
-
|
136
|
+
associations_removed = existing_foreign_keys
|
137
|
+
.select { |foreign_key| !declared_foreign_keys.include?(foreign_key) }
|
138
|
+
.map do |foreign_key|
|
139
|
+
model.reflections.values.find { |reflection| reflection.foreign_key == foreign_key.to_s } ||
|
140
|
+
AssociationRef.new(foreign_key.to_s.delete_suffix('_id').to_sym)
|
141
|
+
end
|
128
142
|
|
129
143
|
model_changes[:associations_added] = associations_added
|
130
144
|
model_changes[:associations_removed] = associations_removed
|
@@ -150,34 +164,34 @@ module RailsFields
|
|
150
164
|
field_type = change[:type]
|
151
165
|
field_type_for_db = field_type[:name]
|
152
166
|
# TODO: custom mapper
|
153
|
-
migration_code << " add_column :#{
|
167
|
+
migration_code << " add_column :#{model.table_name}, :#{change[:name]}, :#{field_type_for_db}"
|
154
168
|
end
|
155
169
|
|
156
170
|
# Handle added associations
|
157
171
|
model_changes.dig(:associations_added)&.each do |assoc|
|
158
|
-
migration_code << " add_reference :#{
|
172
|
+
migration_code << " add_reference :#{model.table_name}, :#{assoc.name}, foreign_key: true"
|
159
173
|
end
|
160
174
|
|
161
175
|
# Handle removed associations
|
162
176
|
model_changes.dig(:associations_removed)&.each do |assoc|
|
163
|
-
migration_code << " remove_reference :#{
|
177
|
+
migration_code << " remove_reference :#{model.table_name}, :#{assoc.name}, foreign_key: true"
|
164
178
|
end
|
165
179
|
|
166
180
|
# Handle removed fields
|
167
181
|
model_changes.dig(:removed)&.each do |change|
|
168
|
-
migration_code << " remove_column :#{
|
182
|
+
migration_code << " remove_column :#{model.table_name}, :#{change[:name]}"
|
169
183
|
end
|
170
184
|
|
171
185
|
# Handle renamed fields
|
172
186
|
model_changes.dig(:renamed)&.each do |change|
|
173
187
|
change_to = change[:to]
|
174
|
-
migration_code << " rename_column :#{
|
188
|
+
migration_code << " rename_column :#{model.table_name}, :#{change[:from]}, :#{change_to}"
|
175
189
|
end
|
176
190
|
|
177
191
|
# Handle fields' type changes
|
178
192
|
model_changes.dig(:type_changed)&.each do |change|
|
179
193
|
change_to = change[:to][:name]
|
180
|
-
migration_code << " change_column :#{
|
194
|
+
migration_code << " change_column :#{model.table_name}, :#{change[:name]}, :#{change_to}"
|
181
195
|
end
|
182
196
|
|
183
197
|
migration_code << " end"
|
@@ -1,29 +1,32 @@
|
|
1
1
|
module RailsFields
|
2
2
|
module Utils
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
# Define maps only if GraphQL is available
|
4
|
+
if defined?(GraphQL)
|
5
|
+
# TODO: mapper can be different or custom
|
6
|
+
GQL_TO_RAILS_TYPE_MAP = {
|
7
|
+
::GraphQL::Types::String => :string,
|
8
|
+
::GraphQL::Types::Int => :integer,
|
9
|
+
::GraphQL::Types::Float => :float,
|
10
|
+
::GraphQL::Types::Boolean => :boolean,
|
11
|
+
::GraphQL::Types::ID => :integer, # or :string depending on how you handle IDs
|
12
|
+
::GraphQL::Types::ISO8601DateTime => :datetime,
|
13
|
+
::GraphQL::Types::ISO8601Date => :date,
|
14
|
+
::GraphQL::Types::JSON => :json,
|
15
|
+
::GraphQL::Types::BigInt => :bigint
|
16
|
+
}.freeze
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
RAILS_TO_GQL_TYPE_MAP = {
|
19
|
+
# id: ::GraphQL::Types::String,
|
20
|
+
string: ::GraphQL::Types::String,
|
21
|
+
integer: ::GraphQL::Types::Int,
|
22
|
+
float: ::GraphQL::Types::Float,
|
23
|
+
boolean: ::GraphQL::Types::Boolean,
|
24
|
+
datetime: ::GraphQL::Types::ISO8601DateTime,
|
25
|
+
date: ::GraphQL::Types::ISO8601Date,
|
26
|
+
json: ::GraphQL::Types::JSON,
|
27
|
+
bigint: ::GraphQL::Types::BigInt,
|
28
|
+
text: ::GraphQL::Types::String
|
29
|
+
}.freeze
|
30
|
+
end
|
28
31
|
end
|
29
32
|
end
|
data/lib/rails_fields/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-fields
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gaston Morixe
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-08-21 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rails
|
@@ -25,7 +24,7 @@ dependencies:
|
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '5.0'
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
27
|
+
name: sqlite3
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - ">="
|
@@ -39,21 +38,7 @@ dependencies:
|
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '0'
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: jekyll
|
41
|
+
name: graphql
|
57
42
|
requirement: !ruby/object:Gem::Requirement
|
58
43
|
requirements:
|
59
44
|
- - ">="
|
@@ -67,7 +52,7 @@ dependencies:
|
|
67
52
|
- !ruby/object:Gem::Version
|
68
53
|
version: '0'
|
69
54
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
55
|
+
name: yard
|
71
56
|
requirement: !ruby/object:Gem::Requirement
|
72
57
|
requirements:
|
73
58
|
- - ">="
|
@@ -92,6 +77,7 @@ extra_rdoc_files: []
|
|
92
77
|
files:
|
93
78
|
- README.md
|
94
79
|
- lib/rails-fields.rb
|
80
|
+
- lib/rails/fields.rb
|
95
81
|
- lib/rails_fields/class_methods.rb
|
96
82
|
- lib/rails_fields/enforce_fields_middleware.rb
|
97
83
|
- lib/rails_fields/errors/rails_fields_error.rb
|
@@ -110,7 +96,6 @@ metadata:
|
|
110
96
|
homepage_uri: https://rails-fields.dev
|
111
97
|
source_code_uri: https://github.com/gastonmorixe/rails-fields
|
112
98
|
bug_tracker_uri: https://github.com/gastonmorixe/rails-fields/issues
|
113
|
-
post_install_message:
|
114
99
|
rdoc_options: []
|
115
100
|
require_paths:
|
116
101
|
- lib
|
@@ -125,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
110
|
- !ruby/object:Gem::Version
|
126
111
|
version: '0'
|
127
112
|
requirements: []
|
128
|
-
rubygems_version: 3.
|
129
|
-
signing_key:
|
113
|
+
rubygems_version: 3.7.1
|
130
114
|
specification_version: 4
|
131
115
|
summary: Enforce field types and attributes for ActiveRecord models in Ruby on Rails
|
132
116
|
applications.
|