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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f22e7663d0f74d05a057fa3ccd9b0777ff675109dd7aa87675b52cc1c4accad
4
- data.tar.gz: b157f7f8119be2e3e723ea6948a223d6d0f048984371d208859c1b5f9d811ccc
3
+ metadata.gz: 72386aef5bd4ef9136597eb8226759ce716deb2d4a899b27837031c3d4678596
4
+ data.tar.gz: b0a8d07705dfba50be8270da36854db49cedc9d2c10f09b292cc48674f059965
5
5
  SHA512:
6
- metadata.gz: b68e8d9810bc4029d3dad204e48a1909b8a7bd76f8014edacd5b198d6d19ecf768be0e3741fab71b90304270f736e0c6fde9bebef4c71cdba7d0c471636af67d
7
- data.tar.gz: e4ff8c3e2d5b051bd5e8324ff920e69394388936c92ee8c13d64684cbcc51819e03559494dc15ad62d6829133bfc91a78b78a45087bfc42450f03062a27fa196
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
- [![Gem Version](https://badge.fury.io/rb/rails-fields.svg)](https://badge.fury.io/rb/rails-fields)
5
+ [![Gem Version](https://badge.fury.io/rb/rails-fields.svg?v=320)](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
- - 🚀 Auto-magic ActiveRecord **Migrations** generation
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)
@@ -0,0 +1,3 @@
1
+ # Autoload shim for hyphenated gem name.
2
+ # RubyGems will try to require 'rails/fields' for 'rails-fields'.
3
+ require "rails-fields"
@@ -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 << OpenStruct.new(name: name.to_s, type:, null:, index:)
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
- type = Class.new(::Types::BaseObject) do
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
- field_gql_type = f.name == :id ? GraphQL::Types::ID : Utils::RAILS_TO_GQL_TYPE_MAP[f.type]
59
- field f.name, field_gql_type
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
 
@@ -14,7 +14,19 @@ module RailsFields
14
14
  end
15
15
 
16
16
  initializer "rails_fields.middleware" do |app|
17
- app.middleware.insert_after ActiveRecord::Migration::CheckPending, RailsFields::EnforceFieldsMiddleware
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
- previous_fields = model.attribute_types.to_h { |k, v| [k.to_sym, v.type] }
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
- # puts "Log: removed_field: #{removed_field}"
92
- added_field = model_changes[:added].find { |f| f[:type] == removed_field[:type] }
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[:type] == rename[:to].to_sym } == 1 &&
106
- model_changes[:removed].count { |f| f[:type] == rename[:from].to_sym } == 1
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.foreign_keys(model.table_name).map(&:options).map { |opt| opt[:column].to_sym }
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.select do |foreign_key|
126
- !declared_foreign_keys.include?(foreign_key)
127
- end.map { |foreign_key| model.reflections.values.find { |reflection| reflection.foreign_key == foreign_key.to_s } }
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 :#{model_name.tableize}, :#{change[:name]}, :#{field_type_for_db}"
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 :#{model_name.tableize}, :#{assoc.name}, foreign_key: true"
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 :#{model_name.tableize}, :#{assoc.name}, foreign_key: true"
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 :#{model_name.tableize}, :#{change[:name]}"
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 :#{model_name.tableize}, :#{change[:from]}, :#{change_to}"
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 :#{model_name.tableize}, :#{change[:name]}, :#{change_to}"
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
- # TODO: mapper can be different or custom
4
- GQL_TO_RAILS_TYPE_MAP = {
5
- ::GraphQL::Types::String => :string,
6
- ::GraphQL::Types::Int => :integer,
7
- ::GraphQL::Types::Float => :float,
8
- ::GraphQL::Types::Boolean => :boolean,
9
- ::GraphQL::Types::ID => :integer, # or :string depending on how you handle IDs
10
- ::GraphQL::Types::ISO8601DateTime => :datetime,
11
- ::GraphQL::Types::ISO8601Date => :date,
12
- ::GraphQL::Types::JSON => :json,
13
- ::GraphQL::Types::BigInt => :bigint
14
- }.freeze
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
- RAILS_TO_GQL_TYPE_MAP = {
17
- # id: ::GraphQL::Types::String,
18
- string: ::GraphQL::Types::String,
19
- integer: ::GraphQL::Types::Int,
20
- float: ::GraphQL::Types::Float,
21
- boolean: ::GraphQL::Types::Boolean,
22
- datetime: ::GraphQL::Types::ISO8601DateTime,
23
- date: ::GraphQL::Types::ISO8601Date,
24
- json: ::GraphQL::Types::JSON,
25
- bigint: ::GraphQL::Types::BigInt,
26
- text: ::GraphQL::Types::String
27
- }.freeze
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
@@ -1,3 +1,3 @@
1
1
  module RailsFields
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.3"
3
3
  end
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.2
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: 2023-08-28 00:00:00.000000000 Z
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: graphql
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: yard
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: webrick
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.4.19
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.