pretty-api 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e020194708396186f0a862e8fcdfe24b4bba1d9ce4449f7261bef0c2a76451d2
4
+ data.tar.gz: fdf19f29e2a101b15a6f2bf4e48fcc84a5ae5bef7469c2eeea6490272cbe66e5
5
+ SHA512:
6
+ metadata.gz: 3a4a6b834fc2d8b2c27be2d27bae71b6c366584227071299dcdf0fbd58fcde1d9225a9051dc470acfbe1213bed65e8556eb7c4cc6dd03df83f2df2f233b3a9ca
7
+ data.tar.gz: 883fd45dfb069e6fe71cde4603265ebab372bfbf0f64007b3aa1e76b164c1a9fdec5f5b143799162a0f8d4477746ea0c33d999b02b569af28bbe99808e92c6ab
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,112 @@
1
+ inherit_gem:
2
+ rubocop-rails-omakase: rubocop.yml
3
+
4
+ inherit_mode:
5
+ merge:
6
+ - Exclude
7
+
8
+ require:
9
+ - rubocop-rails
10
+ - rubocop-performance
11
+ - rubocop-rspec
12
+
13
+ AllCops:
14
+ DisabledByDefault: false
15
+ DisplayCopNames: true
16
+ NewCops: enable
17
+ Exclude:
18
+ - config/initializers/devise.rb
19
+ - lib/templates/**/*
20
+ - public/**/*
21
+
22
+ FactoryBot/ConsistentParenthesesStyle:
23
+ EnforcedStyle: omit_parentheses
24
+
25
+ Layout/LineLength:
26
+ Exclude:
27
+ - config/initializers/simple_form_bootstrap.rb
28
+
29
+ Layout/SpaceInsideArrayLiteralBrackets:
30
+ Enabled: true
31
+ EnforcedStyle: no_space
32
+ EnforcedStyleForEmptyBrackets: no_space
33
+
34
+ Lint/EmptyBlock:
35
+ Enabled: false
36
+
37
+ Lint/UnusedBlockArgument:
38
+ Enabled: false
39
+
40
+ Metrics/AbcSize:
41
+ Max: 20
42
+ Exclude:
43
+ - db/migrate/**/*
44
+ - db/seed/**/*
45
+
46
+ Metrics/BlockLength:
47
+ Enabled: false
48
+
49
+ Metrics/CyclomaticComplexity:
50
+ Max: 10
51
+
52
+ Metrics/MethodLength:
53
+ Max: 15
54
+ Exclude:
55
+ - db/migrate/**/*
56
+ - null
57
+
58
+ Metrics/PerceivedComplexity:
59
+ Max: 10
60
+
61
+ RSpec/AnyInstance:
62
+ Enabled: false
63
+
64
+ RSpec/EmptyExampleGroup:
65
+ Enabled: false
66
+
67
+ RSpec/ExampleLength:
68
+ Max: 10
69
+
70
+ RSpec/MultipleMemoizedHelpers:
71
+ Enabled: false
72
+
73
+ RSpec/NestedGroups:
74
+ Enabled: false
75
+
76
+ RSpec/RepeatedExample:
77
+ Enabled: false
78
+
79
+ Rails/NotNullColumn:
80
+ Enabled: false
81
+
82
+ Rails/OutputSafety:
83
+ Exclude:
84
+ - app/decorators/**/*
85
+ - app/helpers/**/*
86
+
87
+ Style/Documentation:
88
+ Enabled: false
89
+
90
+ Style/EmptyMethod:
91
+ EnforcedStyle: expanded
92
+
93
+ Style/FrozenStringLiteralComment:
94
+ Enabled: false
95
+
96
+ Style/StringLiterals:
97
+ EnforcedStyle: double_quotes
98
+
99
+ Layout/IndentationConsistency:
100
+ Enabled: true
101
+ EnforcedStyle: normal
102
+
103
+ Layout/IndentationWidth:
104
+ Enabled: true
105
+ Width: 2
106
+
107
+ Layout/MultilineMethodCallIndentation:
108
+ Enabled: true
109
+ EnforcedStyle: indented_relative_to_receiver
110
+
111
+ Gemspec/RequireMFA:
112
+ Enabled: false
@@ -0,0 +1,6 @@
1
+ db:
2
+ seeds: spec/app/db/seeds.rb
3
+ migrate: spec/app/db/migrate
4
+ schema: spec/app/db/schema.rb
5
+ config:
6
+ database: spec/app/db/config.yml
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-05-30
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 James St-Pierre
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # PrettyApi
2
+
3
+ Build API that feels like home using native built in Ruby on Rails and ActiveRecord `accepts_nested_attributes_for` but
4
+ without all the boilerplate code that makes your Javascript dirty.
5
+
6
+ ## Comparison
7
+
8
+ Exemple with an organization that has 2 services. Let's say we would like to update one service and destroy the other.
9
+
10
+ #### Without PrettyAPI
11
+
12
+ ```json
13
+ {
14
+ "organization": {
15
+ "id": 1,
16
+ "services_attributes": [
17
+ {
18
+ "id": 1,
19
+ "name": "Service to destroy",
20
+ "_destroy": true
21
+ },
22
+ {
23
+ "id": 2,
24
+ "name": "Service to update"
25
+ }
26
+ ]
27
+ }
28
+ }
29
+ ```
30
+
31
+ ### With PrettyAPI
32
+
33
+ You can omit `_attributes` from your attributes and you can omit everything that you would like to destroy as well.
34
+
35
+ ```json
36
+ {
37
+ "organization": {
38
+ "id": 1,
39
+ "services": [
40
+ {
41
+ "id": 2,
42
+ "name": "Service to update"
43
+ }
44
+ ]
45
+ }
46
+ }
47
+ ```
48
+
49
+ More exemples
50
+
51
+ ```javascript
52
+ {
53
+ organization: {
54
+ services: [] // Delete all services
55
+ }
56
+
57
+ organization: {
58
+ // Fully omit "services" attribute to leave as is
59
+ }
60
+ }
61
+ ```
62
+
63
+ ## How it works
64
+
65
+ Because Rails is a framework built on conventions over configurations, it is possible to use reflections on your
66
+ ActiveRecord models to automatically detect which attributes are expected to be "nested" by declaring properly your
67
+ nested attributes using `accepts_nested_attributes_for`.
68
+
69
+ ## Why
70
+
71
+ In the past I have built many applications that were using frontend frameworks such as React, VueJS and Svelte built on
72
+ top of a Ruby on Rails API. Here are the things that always have irritated me
73
+
74
+ 1. Transforming all my attributes to `_attributes` when the time comes to send my data to the API.
75
+ 2. Keeping destroyed objects in my Arrays only to tell Rails to destroy them by sending `{ id: 1, _destroy: true }`
76
+
77
+ I have tried the approach of working directly with object instances but ActiveRecord behaves unexpectedly by saving
78
+ every associations as soon as you assign the parameters. Here is an exemple
79
+
80
+ ```
81
+ params[:services]
82
+ => { services: [] }
83
+
84
+ @organization.assign_attributes(params)
85
+ => DELETE FROM services WHERE organization_id = 1;
86
+ ```
87
+
88
+ You don't even have time to check for validation or do anything that ActiveRecord already destroyed every services in
89
+ the database that belongs to your organization.
90
+
91
+ ## Installation
92
+
93
+ Add this line to your Gemfile:
94
+
95
+ gem "pretty-api"
96
+
97
+ ## Configuration
98
+
99
+ You can optionally create an initializer to configure these options
100
+ ```
101
+ # Destroy associations that are omitted in your payload
102
+ PretttyApi.destroy_missing_associations = true
103
+ ```
104
+
105
+ ## Usage
106
+
107
+ ```
108
+ class OrganizationsController < ApplicationController
109
+ include PrettyApi::Helpers
110
+
111
+ def create
112
+ @organization = Organization.new
113
+ @organization.assign_attributes(pretty_nested_attributes(@organization, organization_params))
114
+ ...
115
+ end
116
+
117
+ def update
118
+ @organization = Organization.find(...)
119
+ @organization.assign_attributes(pretty_nested_attributes(@organization, organization_params))
120
+ ...
121
+ end
122
+
123
+ private
124
+
125
+ def organization_params
126
+ params.require(:organization).permit(:name, services: [:id, :name])
127
+ end
128
+ end
129
+ ```
130
+
131
+ ## This gem needs more testing
132
+
133
+ While this gem has some unit tests, it hasn't been battle tested yet.
134
+
135
+ ## Beta feature
136
+
137
+ This is a new feature that I am testing to see if I can make validation errors
138
+ over API way easier to work with. By default ActiveRecord doesn't tell you which records in your
139
+ associations are invalid. This is making very hard to highlight the proper input field
140
+ in forms when you are working with nested forms.
141
+
142
+ ```
143
+ include PrettyApi::Helpers
144
+
145
+ @organization.valid?
146
+ => false
147
+
148
+ pretty_nested_errors(@organization)
149
+ => {
150
+ name: ["can't be blank"],
151
+ organizations: {
152
+ 1 => { name: ["can't be blank"] }
153
+ 3 => { name: ["can't be blank"] }
154
+ }
155
+ }
156
+ ```
157
+
158
+ Note: There is a "somewhat" similar feature in ActiveRecord that is not well documented.
159
+ However, while the keys are indexed, they are in string format making it hard
160
+ to work with.
161
+
162
+ ```
163
+ # Per association:
164
+ has_many :my_associations, index_errors: true
165
+
166
+ # Globally:
167
+ config.active_record.index_nested_attribute_errors = true
168
+
169
+ # Before
170
+ product.error.messages
171
+ => {:"variants.display_name"=>["can't be blank"], :"variants.price"=>["can't be blank"]}
172
+
173
+ # After
174
+ product.error.messages
175
+ {:"variants[0].display_name"=>["can't be blank"], :"variants[1].price"=>["can't be blank"]}
176
+ ```
177
+
178
+
179
+ ## Development
180
+
181
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
182
+
183
+ To install this gem onto your local machine, run `bundle exec rake install`.
184
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`,
185
+ which will create a git tag for the version, push git commits and the created tag, and push the `.gem`
186
+ file to [rubygems.org](https://rubygems.org).
187
+
188
+ ## Contributing
189
+
190
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jamesst20/pretty_api.
191
+
192
+ ## License
193
+
194
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+ require 'standalone_migrations'
7
+
8
+ StandaloneMigrations::Tasks.load_tasks
9
+
10
+ task default: :spec
data/internal.md ADDED
@@ -0,0 +1,25 @@
1
+ # Internal documentation
2
+
3
+ This documentation is not meant to be read by anybody. This document will be used for self documentations
4
+ to avoid forgetting important implementations motivations.
5
+
6
+ This gem can get a little confusing sometimes because `accepts_nested_attributes_for` can be used with any kind
7
+ of associations (has_many, belongs_to, has_one, has_many_though, ...) and it can also be used to self reference itself
8
+ or another association that reference itself. We must handle properly these use case to avoid infinite loop.
9
+
10
+ We are able to extract the dependency tree of an association with the internal method `nested_attributes_tree`. This
11
+ method is implemented in two formats: one that returns an array structure, one that returns an hash structure. The library
12
+ itself doesn't make any use of two formats, however we do want to support to type of structure for better user
13
+ experience and compatibility. This is mostly useful for unit testings but this allow users to use the structure they
14
+ want when they pass manually the association tree to the public helpers method.
15
+
16
+ ### Notes on the pretty nested attributes implementation
17
+
18
+ We must never rely on parameters indexes to extract a record association. The order is not guaranteed and this could
19
+ lead to odd behavior or potential accidental data loss. We must rely on the primary key of the record against the
20
+ given parameter. If not done properly, it would append in the parameters `{ id: ..., _destroy: true}` wrongly thinking
21
+ some associations dont exist. I believe ActiveRecord would raise a RecordNotFound exception to protect against this
22
+ scenario, however we have unit tests to protect against this scenario to avoid code regression in the future.
23
+
24
+ Internally it infers automatically the dependency tree of a record by calling `nested_attributes_tree` or by receiving
25
+ an hash or an array by the user explicitly. This is subject to the spoken hash or array format spoken earlier.
data/lib/pretty-api.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "pretty_api" # rubocop:disable Naming/FileName
@@ -0,0 +1,73 @@
1
+ module PrettyApi
2
+ module ActiveRecord
3
+ class Associations
4
+ def self.nested_attributes_tree(model, structure = :array)
5
+ if structure == :array
6
+ nested_attributes_tree_array(model)
7
+ elsif structure == :hash
8
+ nested_attributes_tree_hash(model)
9
+ end
10
+ end
11
+
12
+ def self.nested_attributes_tree_hash(model, depth = {})
13
+ nested_attributes_descriptions(model).index_by { |a| a[:id] }.each_with_object({}) do |(key, assoc), result|
14
+ depth[key] ||= 0
15
+
16
+ next unless depth[key] < PrettyApi.max_nested_attributes_depth
17
+
18
+ depth[key] += 1
19
+ result[assoc[:name]] = nested_attributes_tree_hash(assoc[:model], depth)
20
+ depth[key] -= 1
21
+ end
22
+ end
23
+
24
+ def self.nested_attributes_tree_array(model, depth = {})
25
+ nested_attributes_descriptions(model).index_by { |a| a[:id] }.map do |(key, association)|
26
+ depth[key] ||= 0
27
+
28
+ next nil if depth[key] >= PrettyApi.max_nested_attributes_depth
29
+
30
+ depth[key] += 1
31
+ result = { association[:name] => nested_attributes_tree_array(association[:model], depth) }
32
+ depth[key] -= 1
33
+
34
+ result.compact_blank.blank? ? association[:name] : result
35
+ end.compact_blank
36
+ end
37
+
38
+ def self.nested_attributes_descriptions(model)
39
+ model.nested_attributes_options.keys.map do |association_name|
40
+ association_model = attribute_association_class(model, association_name)
41
+ {
42
+ id: "#{model}_#{association_name}",
43
+ name: association_name,
44
+ model: association_model,
45
+ associations: association_model.nested_attributes_options.keys.map do |n|
46
+ attribute_association_class(association_model, n)
47
+ end
48
+ }
49
+ end
50
+ end
51
+
52
+ def self.attribute_destroy_allowed?(model, attribute)
53
+ model.nested_attributes_options[attribute.to_sym][:allow_destroy] == true
54
+ end
55
+
56
+ def self.attribute_association(model, attribute)
57
+ model.reflect_on_association(attribute).chain.last
58
+ end
59
+
60
+ def self.attribute_association_class(model, attribute)
61
+ model.reflect_on_association(attribute).class_name.constantize
62
+ end
63
+
64
+ def self.association_type(association)
65
+ association.macro
66
+ end
67
+
68
+ def self.association_primary_key(association)
69
+ association.class_name.constantize.primary_key
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,9 @@
1
+ module PrettyApi
2
+ module ActiveRecord
3
+ class Orm
4
+ def self.where_not(record, attribute, values)
5
+ record.where.not(attribute => values)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ module PrettyApi
2
+ module Errors
3
+ class NestedErrors
4
+ def self.parsed_nested_errors(record, attrs)
5
+ errors = record_only_errors(record)
6
+
7
+ return errors if attrs.blank?
8
+
9
+ parse_deep_nested_errors(record, attrs, errors)
10
+
11
+ PrettyApi::Utils::Hash.deep_compact_blank(errors)
12
+ end
13
+
14
+ def self.parse_deep_nested_errors(record, attrs, result, parent_record = nil)
15
+ case attrs
16
+ when Hash
17
+ attrs.each do |key, value|
18
+ parse_association_errors(record, key, value, result, parent_record)
19
+ end
20
+ when Array
21
+ attrs.each { |value| parse_deep_nested_errors record, value, result, parent_record }
22
+ else
23
+ parse_association_errors(record, attrs, nil, result, parent_record)
24
+ end
25
+ end
26
+
27
+ def self.parse_association_errors(record, attr, nested_attrs, result, parent_record)
28
+ association = record.send(attr)
29
+
30
+ return if association.blank?
31
+ return if association == parent_record
32
+
33
+ if association.respond_to? :to_a
34
+ parse_has_many_errors(record, association, attr, nested_attrs, result)
35
+ else
36
+ parse_has_one_errors(record, association, attr, nested_attrs, result)
37
+ end
38
+ end
39
+
40
+ def self.parse_has_many_errors(record, associations, attr, nested_attrs, result)
41
+ result[attr] = {}
42
+ associations.each_with_index do |association, i|
43
+ result[attr][i] = record_only_errors(association)
44
+ parse_deep_nested_errors association, nested_attrs, result[attr][i], record if nested_attrs.present?
45
+ end
46
+ end
47
+
48
+ def self.parse_has_one_errors(record, association, attr, nested_attrs, result)
49
+ result[attr] = record_only_errors(association)
50
+ parse_deep_nested_errors association, nested_attrs, result[attr], record if nested_attrs.present?
51
+ end
52
+
53
+ def self.record_only_errors(record)
54
+ record.errors.as_json.reject { |k, _v| k.to_s.include?(".") }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ module PrettyApi
2
+ module Helpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def pretty_nested_attributes(record, params, attrs = nil)
7
+ params = params.to_h.with_indifferent_access
8
+
9
+ attrs ||= PrettyApi::ActiveRecord::Associations.nested_attributes_tree(record.class)
10
+
11
+ PrettyApi::Parameters::NestedAttributes.parse_nested_attributes(record, params, attrs)
12
+ end
13
+
14
+ def pretty_nested_errors(record, attrs = nil)
15
+ attrs ||= PrettyApi::ActiveRecord::Associations.nested_attributes_tree(record.class)
16
+
17
+ PrettyApi::Errors::NestedErrors.parsed_nested_errors(record, attrs)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,82 @@
1
+ module PrettyApi
2
+ module Parameters
3
+ class NestedAttributes
4
+ def self.parse_nested_attributes(record, params, attrs)
5
+ return {} if params.blank?
6
+
7
+ case attrs
8
+ when Hash, Array
9
+ parse_deep_nested_attributes(record, params, attrs)
10
+ when String, Symbol
11
+ if params.key?(attrs)
12
+ include_associations_to_destroy(record, params, attrs)
13
+ params["#{attrs}_attributes"] = params.delete(attrs)
14
+ end
15
+ end
16
+
17
+ params
18
+ end
19
+
20
+ def self.parse_deep_nested_attributes(record, params, attrs)
21
+ case attrs
22
+ when Hash
23
+ attrs.each do |assoc_key, nested_assoc|
24
+ if params[assoc_key].is_a? Array
25
+ parse_has_many_association(record, params, assoc_key, nested_assoc)
26
+ else
27
+ parse_has_one_association(record, params, assoc_key, nested_assoc)
28
+ end
29
+ parse_nested_attributes(record, params, assoc_key)
30
+ end
31
+ when Array
32
+ attrs.each { |assoc_or_nested_assoc| parse_nested_attributes(record, params, assoc_or_nested_assoc) }
33
+ end
34
+ end
35
+
36
+ def self.parse_has_many_association(record, params, assoc_key, nested_assoc)
37
+ params[assoc_key].each do |p|
38
+ assoc_primary_key = record.try(:class).try(:primary_key)
39
+ assoc = record.try(assoc_key).try(:detect) { |r| r.try(assoc_primary_key) == p[assoc_primary_key] }
40
+ parse_nested_attributes(assoc, p, nested_assoc)
41
+ end
42
+ end
43
+
44
+ def self.parse_has_one_association(record, params, assoc_key, nested_assoc)
45
+ parse_nested_attributes(record.try(assoc_key), params[assoc_key], nested_assoc)
46
+ end
47
+
48
+ def self.include_associations_to_destroy(record, params, attr)
49
+ return unless PrettyApi.destroy_missing_associations && record.present?
50
+
51
+ association = PrettyApi::ActiveRecord::Associations.attribute_association(record.class, attr)
52
+
53
+ return unless PrettyApi::ActiveRecord::Associations.attribute_destroy_allowed?(record.class, attr)
54
+
55
+ primary_key = PrettyApi::ActiveRecord::Associations.association_primary_key(association)
56
+
57
+ assoc_type = PrettyApi::ActiveRecord::Associations.association_type(association)
58
+
59
+ include_has_many_to_destroy(record, params, attr, primary_key) if assoc_type == :has_many
60
+ include_has_one_to_destroy(record, params, attr, primary_key) if assoc_type.in?(%i[has_one belongs_to])
61
+ end
62
+
63
+ def self.include_has_many_to_destroy(record, params, attr, primary_key)
64
+ ids_to_destroy = PrettyApi::ActiveRecord::Orm
65
+ .where_not(record.send(attr), primary_key, params[attr].pluck(primary_key))
66
+ .pluck(primary_key)
67
+
68
+ params[attr].push(*ids_to_destroy.map { |id| { primary_key => id, _destroy: true } })
69
+ end
70
+
71
+ def self.include_has_one_to_destroy(record, params, attr, primary_key)
72
+ association_id = record.send(attr).try(primary_key)
73
+
74
+ return if association_id.blank?
75
+ return if params[attr].try(:[], primary_key).present?
76
+
77
+ params[attr] ||= {}
78
+ params[attr].merge!({ primary_key => association_id, _destroy: true })
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,13 @@
1
+ module PrettyApi
2
+ module Utils
3
+ class Hash
4
+ def self.deep_compact_blank(hash)
5
+ hash.each_with_object({}) do |(k, v), new_hash|
6
+ v = deep_compact_blank(v) if v.is_a? ::Hash
7
+ v = v.compact_blank if v.is_a? ::Array
8
+ new_hash[k] = v if v.present?
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module PrettyApi
2
+ VERSION = "0.2.0".freeze
3
+ end
data/lib/pretty_api.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "active_support/concern"
2
+
3
+ require_relative "pretty_api/version"
4
+ require_relative "pretty_api/utils/hash"
5
+ require_relative "pretty_api/active_record/orm"
6
+ require_relative "pretty_api/errors/nested_errors"
7
+ require_relative "pretty_api/helpers"
8
+ require_relative "pretty_api/active_record/associations"
9
+ require_relative "pretty_api/parameters/nested_attributes"
10
+
11
+ module PrettyApi
12
+ singleton_class.attr_accessor :destroy_missing_associations
13
+ self.destroy_missing_associations = true
14
+
15
+ singleton_class.attr_accessor :max_nested_attributes_depth
16
+ self.max_nested_attributes_depth = 1
17
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pretty-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - James St-Pierre
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-06-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ description: Simplify the usage of accepts_nested_attributes_for in Rails applications.
28
+ email:
29
+ - Jamesst20@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - ".rubocop.yml"
36
+ - ".standalone_migrations"
37
+ - CHANGELOG.md
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - internal.md
42
+ - lib/pretty-api.rb
43
+ - lib/pretty_api.rb
44
+ - lib/pretty_api/active_record/associations.rb
45
+ - lib/pretty_api/active_record/orm.rb
46
+ - lib/pretty_api/errors/nested_errors.rb
47
+ - lib/pretty_api/helpers.rb
48
+ - lib/pretty_api/parameters/nested_attributes.rb
49
+ - lib/pretty_api/utils/hash.rb
50
+ - lib/pretty_api/version.rb
51
+ homepage: https://github.com/jamesst20/pretty_api
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/jamesst20/pretty_api
56
+ source_code_uri: https://github.com/jamesst20/pretty_api
57
+ changelog_uri: https://github.com/jamesst20/pretty_api/blob/master/CHANGELOG.md
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.0.0
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.5.9
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Pretty API for Rails
77
+ test_files: []