rails-fields 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +39 -0
- data/lib/rails_fields/class_methods.rb +125 -0
- data/lib/rails_fields/errors/rails_fields_error.rb +5 -0
- data/lib/rails_fields/errors/rails_fields_mismatch_error.rb +18 -0
- data/lib/rails_fields/errors/rails_fields_unknown_type_error.rb +5 -0
- data/lib/rails_fields/instance_methods.rb +4 -0
- data/lib/rails_fields/utils/helpers.rb +202 -0
- data/lib/rails_fields/utils/logging.rb +11 -0
- data/lib/rails_fields/utils/mappings.rb +29 -0
- data/lib/rails_fields/version.rb +3 -0
- data/lib/rails_fields.rb +26 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f6148f3e28ada22e83ccdd3d5c9ebe0054a61e61d28af95fcac0052b42e03ef5
|
4
|
+
data.tar.gz: 9095670043d7f5df6fc540739c2b81951d16b2abc4da9b901ce3f58c7aa71515
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 16125dfe4d17cc6b2c12465bf7182a0e73d0be3e2e79efb33b6feaa8272bc79bed026bc4ff5f35d3cf1bf27ed23c6b17151083827b55f9cf2f09be80b7410782
|
7
|
+
data.tar.gz: 956dc90b7ee6e99f1a0fb75987d86b1d0f88db2e294d9ff334c77f06da1476cc29fb538d4c2e7748b55f1f7e76b24a8bd574328d5e2a33be22524cb81536d21e
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Rails Fields
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
Enforce field types and attributes for ActiveRecord models in Ruby on Rails applications.
|
5
|
+
|
6
|
+
## Description
|
7
|
+
The `rails-fields` gem provides robust field type enforcement for ActiveRecord models in Ruby on Rails applications. It includes utility methods for type validation, logging, and field mappings between GraphQL and ActiveRecord types. Custom error classes provide clear diagnostics for field-related issues, making it easier to maintain consistent data models.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'rails-fields'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
$ bundle install
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ gem install rails-fields
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
(TBD: Add usage examples here)
|
32
|
+
|
33
|
+
## License
|
34
|
+
|
35
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
36
|
+
|
37
|
+
## Author
|
38
|
+
|
39
|
+
Gaston Morixe - gaston@gastonmorixe.com
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module RailsFields
|
2
|
+
module ClassMethods
|
3
|
+
# TODO: Check all models at rails init app? like migrations?
|
4
|
+
|
5
|
+
def declared_fields
|
6
|
+
@declared_fields ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def declared_fields=(value)
|
10
|
+
@declared_fields = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def write_migration(index: nil)
|
14
|
+
changes = RailsFields::Utils.detect_changes(self)
|
15
|
+
RailsFields::Utils.generate_migration(self, changes, index:, write: true)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Declares a field with enforcement.
|
19
|
+
#
|
20
|
+
# @!method
|
21
|
+
# @param name [Symbol] the name of the field
|
22
|
+
# @param type [Symbol] the type of the field
|
23
|
+
# @param null [Boolean] whether the field can be null (default: true)
|
24
|
+
# @param index [Boolean] whether to index the field (default: false)
|
25
|
+
# @return [void]
|
26
|
+
#
|
27
|
+
# @!macro [attach] field
|
28
|
+
# @!attribute $1
|
29
|
+
# @return [$2] the $1 property
|
30
|
+
def field(name, type, null: true, index: false)
|
31
|
+
# Check if type is a valid GraphQL type
|
32
|
+
# GraphQL::Types.const_get(type) if type.is_a?(Symbol) || type.is_a?(String)
|
33
|
+
unless Utils.valid_type?(type)
|
34
|
+
raise Errors::RailsFieldsUnknownTypeError.new("
|
35
|
+
Declared field '#{name}' in class '#{self.name}' of unknown type '#{type}'. Allowed types are: #{Utils.allowed_types.join(', ')}.
|
36
|
+
")
|
37
|
+
end
|
38
|
+
|
39
|
+
declared_fields << OpenStruct.new(name: name.to_s, type:, null:, index:)
|
40
|
+
end
|
41
|
+
|
42
|
+
def gql_type
|
43
|
+
return RailsFields.processed_classes[self] if RailsFields.processed_classes[self].present?
|
44
|
+
|
45
|
+
fields = declared_fields
|
46
|
+
owner_self = self
|
47
|
+
|
48
|
+
type = Class.new(::Types::BaseObject) do
|
49
|
+
# graphql_name "#{owner_self.name}Type"
|
50
|
+
graphql_name "#{owner_self.name}"
|
51
|
+
description "A type representing a #{owner_self.name}"
|
52
|
+
|
53
|
+
fields.each do |f|
|
54
|
+
next if f.type.nil? # TODO: ! remove references fields
|
55
|
+
|
56
|
+
# Assuming a proper mapping from your custom types to GraphQL types
|
57
|
+
# 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
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Cache the processed class here to prevent infinite recursion
|
64
|
+
RailsFields.processed_classes[self] = type
|
65
|
+
|
66
|
+
type.instance_eval do
|
67
|
+
owner_self.reflections.each do |association_name, reflection|
|
68
|
+
if reflection.macro == :has_many
|
69
|
+
reflection_klass = if reflection.options[:through]
|
70
|
+
through_reflection_klass = reflection.through_reflection.klass
|
71
|
+
source_reflection_name = reflection.source_reflection_name.to_s
|
72
|
+
source_reflection = through_reflection_klass.reflections[source_reflection_name]
|
73
|
+
source_reflection ? source_reflection.klass : through_reflection_klass
|
74
|
+
else
|
75
|
+
reflection.klass
|
76
|
+
end
|
77
|
+
field association_name, [reflection_klass.gql_type], null: true
|
78
|
+
elsif reflection.macro == :belongs_to
|
79
|
+
field association_name, reflection.klass.gql_type, null: true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
type
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def enforce_declared_fields
|
88
|
+
database_columns = column_names.map(&:to_sym)
|
89
|
+
declared_fields_names = declared_fields.map(&:name).map(&:to_sym) || []
|
90
|
+
changes = RailsFields::Utils.detect_changes(self)
|
91
|
+
migration = RailsFields::Utils.generate_migration(self, changes)
|
92
|
+
instance_methods = self.instance_methods(false).select do |method|
|
93
|
+
instance_method(method).source_location.first.start_with?(Rails.root.to_s)
|
94
|
+
end
|
95
|
+
extra_methods = instance_methods - declared_fields_names.map(&:to_sym)
|
96
|
+
has_changes = !changes.nil?
|
97
|
+
|
98
|
+
unless extra_methods.empty?
|
99
|
+
# TODO: Custom error subclass
|
100
|
+
raise "You have extra methods declared in #{name}: #{extra_methods.join(', ')}. Please remove them or declare them as fields."
|
101
|
+
end
|
102
|
+
|
103
|
+
if has_changes
|
104
|
+
error_message = <<~STRING
|
105
|
+
|
106
|
+
----------------
|
107
|
+
|
108
|
+
Declared Fields:
|
109
|
+
#{declared_fields_names.join(', ')}
|
110
|
+
|
111
|
+
Database columns:
|
112
|
+
#{database_columns.join(', ')}
|
113
|
+
|
114
|
+
Changes:
|
115
|
+
#{changes.to_yaml.lines[1..-1].join}
|
116
|
+
Migration:
|
117
|
+
#{migration}
|
118
|
+
|
119
|
+
----------------
|
120
|
+
STRING
|
121
|
+
raise Errors::RailsFieldsMismatchError.new(error_message)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RailsFields
|
2
|
+
module Errors
|
3
|
+
class RailsFieldsMismatchError < RailsFieldsError
|
4
|
+
include ActiveSupport::ActionableError
|
5
|
+
|
6
|
+
action "Save migrations" do
|
7
|
+
models = RailsFields::Utils.active_record_models
|
8
|
+
models.each_with_index do |m, index|
|
9
|
+
m.write_migration(index:)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# action "Run db:migrations" do
|
14
|
+
# ActiveRecord::Tasks::DatabaseTasks.migrate
|
15
|
+
# end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module RailsFields
|
2
|
+
module Utils
|
3
|
+
class << self
|
4
|
+
def allowed_types
|
5
|
+
# TODO: this may depend on the current database adapter or mapper
|
6
|
+
ActiveRecord::Base.connection.native_database_types.keys
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid_type?(type)
|
10
|
+
# TODO: this may depend on the current database adapter or mapper
|
11
|
+
allowed_types.include?(type)
|
12
|
+
end
|
13
|
+
|
14
|
+
def active_record_models
|
15
|
+
Rails.application.eager_load! # Ensure all models are loaded
|
16
|
+
|
17
|
+
ActiveRecord::Base.descendants.reject do |model|
|
18
|
+
!(model.is_a?(Class) && model < ApplicationRecord) ||
|
19
|
+
model.abstract_class? ||
|
20
|
+
model.name.nil? ||
|
21
|
+
model.name == "ActiveRecord::SchemaMigration"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Detect changes between the ActiveRecord model declared fields and the database structure.
|
26
|
+
# @example
|
27
|
+
# model_changes = FieldEnforcement::Utils.detect_changes(User)
|
28
|
+
# # => {
|
29
|
+
# added: [],
|
30
|
+
# removed: [],
|
31
|
+
# renamed: [],
|
32
|
+
# type_changed: [],
|
33
|
+
# potential_renames: []
|
34
|
+
# }
|
35
|
+
# @param model [ActiveRecord::Base] the model to check
|
36
|
+
# @return [Hash, Nil] the changes detected
|
37
|
+
def detect_changes(model)
|
38
|
+
previous_fields = model.attribute_types.to_h { |k, v| [k.to_sym, v.type] }
|
39
|
+
declared_fields = model.declared_fields.to_h do |f|
|
40
|
+
[f.name.to_sym, {
|
41
|
+
name: f.type.to_sym,
|
42
|
+
options: f.options
|
43
|
+
}]
|
44
|
+
end
|
45
|
+
|
46
|
+
LOGGER.debug "Log: previous_fields: #{previous_fields}"
|
47
|
+
LOGGER.debug "Log: declared_fields #{declared_fields}}"
|
48
|
+
|
49
|
+
model_changes = {
|
50
|
+
added: [],
|
51
|
+
removed: [],
|
52
|
+
renamed: [],
|
53
|
+
type_changed: [],
|
54
|
+
potential_renames: [],
|
55
|
+
associations_added: [],
|
56
|
+
associations_removed: []
|
57
|
+
}
|
58
|
+
|
59
|
+
# Detect added and type-changed fields
|
60
|
+
declared_fields.each do |name, type|
|
61
|
+
type_name = type[:name]
|
62
|
+
if previous_fields[name]
|
63
|
+
if previous_fields[name] != type_name
|
64
|
+
model_changes[:type_changed] << { name:, from: previous_fields[name],
|
65
|
+
to: type }
|
66
|
+
end
|
67
|
+
else
|
68
|
+
model_changes[:added] << { name:, type: }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
LOGGER.debug "Log: model_changes[:added] before filter #{model_changes[:added]}"
|
73
|
+
# Remove added fields that have a defined method in the the model
|
74
|
+
model_changes[:added] = model_changes[:added].filter { |f| !model.instance_methods.include?(f[:name]) }
|
75
|
+
LOGGER.debug "Log: model_changes[:added] after filter #{model_changes[:added]}"
|
76
|
+
|
77
|
+
# Detect removed fields
|
78
|
+
removed_fields = previous_fields.keys - declared_fields.keys
|
79
|
+
model_changes[:removed] = removed_fields.map { |name| { name:, type: previous_fields[name] } }
|
80
|
+
|
81
|
+
LOGGER.debug "Log: model_changes[:removed] 1 #{model_changes[:removed]}"
|
82
|
+
|
83
|
+
# Remove foreign keys from removed fields
|
84
|
+
associations = model.reflections.values.map(&:foreign_key).map(&:to_sym)
|
85
|
+
model_changes[:removed].reject! { |f| associations.include?(f[:name]) }
|
86
|
+
|
87
|
+
LOGGER.debug "Log: model_changes[:removed] 2 #{model_changes[:removed]} | associations #{associations}"
|
88
|
+
|
89
|
+
# Detect potential renames
|
90
|
+
potential_renames = []
|
91
|
+
model_changes[:removed].each do |removed_field|
|
92
|
+
# puts "Log: removed_field: #{removed_field}"
|
93
|
+
added_field = model_changes[:added].find { |f| f[:type] == removed_field[:type] }
|
94
|
+
if added_field
|
95
|
+
potential_renames << { from: removed_field[:name],
|
96
|
+
to: added_field[:name] }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
LOGGER.debug "Log: potential_renames: #{potential_renames}"
|
101
|
+
|
102
|
+
model_changes[:potential_renames] = potential_renames
|
103
|
+
|
104
|
+
# Filter out incorrect renames (one-to-one mapping)
|
105
|
+
potential_renames.each do |rename|
|
106
|
+
next unless model_changes[:added].count { |f| f[:type] == rename[:to].to_sym } == 1 &&
|
107
|
+
model_changes[:removed].count { |f| f[:type] == rename[:from].to_sym } == 1
|
108
|
+
|
109
|
+
model_changes[:renamed] << rename
|
110
|
+
model_changes[:added].reject! { |f| f[:name] == rename[:to].to_sym }
|
111
|
+
model_changes[:removed].reject! { |f| f[:name] == rename[:from].to_sym }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Handle associations changes
|
115
|
+
declared_associations = model.reflections.values.select do |reflection|
|
116
|
+
[:belongs_to].include?(reflection.macro)
|
117
|
+
end
|
118
|
+
|
119
|
+
declared_foreign_keys = declared_associations.map(&:foreign_key).map(&:to_sym)
|
120
|
+
existing_foreign_keys = ActiveRecord::Base.connection.foreign_keys(model.table_name).map(&:options).map { |opt| opt[:column].to_sym }
|
121
|
+
|
122
|
+
associations_added = declared_associations.select do |reflection|
|
123
|
+
!existing_foreign_keys.include?(reflection.foreign_key.to_sym)
|
124
|
+
end
|
125
|
+
|
126
|
+
associations_removed = existing_foreign_keys.select do |foreign_key|
|
127
|
+
!declared_foreign_keys.include?(foreign_key)
|
128
|
+
end.map { |foreign_key| model.reflections.values.find { |reflection| reflection.foreign_key == foreign_key.to_s } }
|
129
|
+
|
130
|
+
model_changes[:associations_added] = associations_added
|
131
|
+
model_changes[:associations_removed] = associations_removed
|
132
|
+
|
133
|
+
return model_changes unless model_changes.values.all?(&:empty?)
|
134
|
+
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def generate_migration(model, model_changes, index: 0, write: false)
|
139
|
+
return if model_changes.blank?
|
140
|
+
|
141
|
+
model_name = model.name
|
142
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i + index
|
143
|
+
migration_class_name = "#{model_name}Migration#{timestamp}"
|
144
|
+
|
145
|
+
migration_code = []
|
146
|
+
migration_code << "class #{migration_class_name} < ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
|
147
|
+
|
148
|
+
migration_code << " def change"
|
149
|
+
|
150
|
+
model_changes.dig(:added)&.each do |change|
|
151
|
+
field_type = change[:type]
|
152
|
+
field_type_for_db = field_type[:name]
|
153
|
+
# TODO: custom mapper
|
154
|
+
migration_code << " add_column :#{model_name.tableize}, :#{change[:name]}, :#{field_type_for_db}"
|
155
|
+
end
|
156
|
+
|
157
|
+
# Handle added associations
|
158
|
+
model_changes.dig(:associations_added)&.each do |assoc|
|
159
|
+
migration_code << " add_reference :#{model_name.tableize}, :#{assoc.name}, foreign_key: true"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Handle removed associations
|
163
|
+
model_changes.dig(:associations_removed)&.each do |assoc|
|
164
|
+
migration_code << " remove_reference :#{model_name.tableize}, :#{assoc.name}, foreign_key: true"
|
165
|
+
end
|
166
|
+
|
167
|
+
# Handle removed fields
|
168
|
+
model_changes.dig(:removed)&.each do |change|
|
169
|
+
migration_code << " remove_column :#{model_name.tableize}, :#{change[:name]}"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Handle renamed fields
|
173
|
+
model_changes.dig(:renamed)&.each do |change|
|
174
|
+
change_to = change[:to]
|
175
|
+
migration_code << " rename_column :#{model_name.tableize}, :#{change[:from]}, :#{change_to}"
|
176
|
+
end
|
177
|
+
|
178
|
+
# Handle fields' type changes
|
179
|
+
model_changes.dig(:type_changed)&.each do |change|
|
180
|
+
change_to = change[:to]
|
181
|
+
migration_code << " change_column :#{model_name.tableize}, :#{change[:name]}, :#{change_to}"
|
182
|
+
end
|
183
|
+
|
184
|
+
migration_code << " end"
|
185
|
+
migration_code << "end"
|
186
|
+
migration_code << ""
|
187
|
+
|
188
|
+
write_migration(migration_code, migration_class_name, timestamp) if write
|
189
|
+
|
190
|
+
migration_code.join("\n")
|
191
|
+
end
|
192
|
+
|
193
|
+
def write_migration(migration_code, migration_class_name, timestamp)
|
194
|
+
migration_filename = "#{timestamp}_#{migration_class_name.underscore}.rb"
|
195
|
+
migration_path = Rails.root.join("db", "migrate", migration_filename)
|
196
|
+
File.write(migration_path, migration_code.join("\n"))
|
197
|
+
LOGGER.info "Migration saved at #{migration_path}"
|
198
|
+
{ migration_filename:, migration_path: }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RailsFields
|
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
|
15
|
+
|
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
|
28
|
+
end
|
29
|
+
end
|
data/lib/rails_fields.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "rails_fields/errors/rails_fields_error"
|
2
|
+
require "rails_fields/errors/rails_fields_mismatch_error"
|
3
|
+
require "rails_fields/errors/rails_fields_unknown_type_error"
|
4
|
+
require "rails_fields/utils/logging"
|
5
|
+
require "rails_fields/utils/mappings"
|
6
|
+
require "rails_fields/utils/helpers"
|
7
|
+
require "rails_fields/class_methods"
|
8
|
+
require "rails_fields/instance_methods"
|
9
|
+
|
10
|
+
# Provides enforcement of declared field for ActiveRecord models.
|
11
|
+
module RailsFields
|
12
|
+
@processed_classes = {}
|
13
|
+
|
14
|
+
def self.processed_classes
|
15
|
+
@processed_classes
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param base [ActiveRecord::Base] the model to include the module in
|
19
|
+
def self.included(base)
|
20
|
+
# base.extend(ClassMethods)
|
21
|
+
# todo: raise if class methods not found
|
22
|
+
base.after_initialize do
|
23
|
+
self.class.enforce_declared_fields
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails-fields
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gaston Morixe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-08-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: The rails-fields gem provides robust field type enforcement for ActiveRecord
|
14
|
+
models in Ruby on Rails applications. It includes utility methods for type validation,
|
15
|
+
logging, and field mappings between GraphQL and ActiveRecord types. Custom error
|
16
|
+
classes provide clear diagnostics for field-related issues, making it easier to
|
17
|
+
maintain consistent data models.
|
18
|
+
email:
|
19
|
+
- gaston@gastonmorixe.com
|
20
|
+
executables: []
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- README.md
|
25
|
+
- lib/rails_fields.rb
|
26
|
+
- lib/rails_fields/class_methods.rb
|
27
|
+
- lib/rails_fields/errors/rails_fields_error.rb
|
28
|
+
- lib/rails_fields/errors/rails_fields_mismatch_error.rb
|
29
|
+
- lib/rails_fields/errors/rails_fields_unknown_type_error.rb
|
30
|
+
- lib/rails_fields/instance_methods.rb
|
31
|
+
- lib/rails_fields/utils/helpers.rb
|
32
|
+
- lib/rails_fields/utils/logging.rb
|
33
|
+
- lib/rails_fields/utils/mappings.rb
|
34
|
+
- lib/rails_fields/version.rb
|
35
|
+
homepage: https://github.com/gastonmorixe/rails-fields
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.2.2
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubygems_version: 3.4.19
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Enforce field types and attributes for ActiveRecord models in Ruby on Rails
|
58
|
+
applications.
|
59
|
+
test_files: []
|