praxis 2.0.pre.10 → 2.0.pre.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/CHANGELOG.md +9 -0
- data/bin/praxis +59 -2
- data/lib/praxis/bootloader_stages/environment.rb +1 -0
- data/lib/praxis/docs/open_api_generator.rb +1 -1
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +57 -8
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
- data/lib/praxis/extensions/pagination.rb +5 -32
- data/lib/praxis/mapper/active_model_compat.rb +4 -0
- data/lib/praxis/mapper/resource.rb +18 -2
- data/lib/praxis/mapper/selector_generator.rb +1 -0
- data/lib/praxis/mapper/sequel_compat.rb +7 -0
- data/lib/praxis/plugins/mapper_plugin.rb +22 -13
- data/lib/praxis/plugins/pagination_plugin.rb +34 -4
- data/lib/praxis/tasks/api_docs.rb +4 -1
- data/lib/praxis/version.rb +1 -1
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +15 -2
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
- data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
- data/tasks/thor/example.rb +12 -6
- data/tasks/thor/model.rb +40 -0
- data/tasks/thor/scaffold.rb +117 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +1 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +9 -2
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +2 -2
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +7 -28
- data/tasks/thor/templates/generator/example_app/config.ru +1 -2
- data/tasks/thor/templates/generator/example_app/config/environment.rb +2 -1
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +3 -2
- data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +4 -4
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +1 -6
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +4 -2
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +2 -2
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +2 -2
- data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
- data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
- data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
- metadata +14 -2
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module V1
|
4
|
+
module Resources
|
5
|
+
class Base < Praxis::Mapper::Resource
|
6
|
+
# Base for all V1 resources.
|
7
|
+
# Resources withing a single version should have resource mappings separate from other versions
|
8
|
+
# and the Mapper::Resource will appropriately maintain different model_maps for each Base classes
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -2,42 +2,21 @@
|
|
2
2
|
|
3
3
|
module V1
|
4
4
|
module Resources
|
5
|
-
class User <
|
5
|
+
class User < Base
|
6
6
|
model ::User
|
7
7
|
|
8
|
-
# Mappings for the allowed
|
8
|
+
# Mappings for the allowed filters
|
9
9
|
filters_mapping(
|
10
|
-
'
|
11
|
-
'first_name': 'first_name',
|
12
|
-
|
13
|
-
'
|
14
|
-
case spec[:value].to_s
|
15
|
-
when 'pending' # Pending users do not have a uuid
|
16
|
-
{ name: :uuid, value: nil, op: spec[:op] }
|
17
|
-
when 'active' # Active users do not have a uuid (so "flip" the original equality condition)
|
18
|
-
opposite_op = spec[:op] == '=' ? '!=' : '='
|
19
|
-
{ name: :uuid, value: nil, op: opposite_op }
|
20
|
-
else
|
21
|
-
raise "Cannot filter users by state #{spec[:value]}"
|
22
|
-
end
|
23
|
-
end,
|
10
|
+
'uuid': 'uuid',
|
11
|
+
'first_name': 'first_name',
|
12
|
+
'last_name': 'last_name',
|
13
|
+
'email': 'email'
|
24
14
|
)
|
25
15
|
|
26
|
-
# Example of a property that depends on a differently named DB field
|
27
|
-
property :uid, dependencies: %i[id]
|
28
16
|
# To compute the full_name (method below) we need to load first and last names from the DB
|
29
17
|
property :full_name, dependencies: %i[first_name last_name]
|
30
18
|
|
31
|
-
|
32
|
-
id # underlying id field of the model
|
33
|
-
end
|
34
|
-
|
35
|
-
# Computed attribute: if uuid nil, user in in a pending stat, else active
|
36
|
-
def state
|
37
|
-
self.uuid.nil? ? 'pending' : 'active'
|
38
|
-
end
|
39
|
-
|
40
|
-
# Computed attribute the combines first and last
|
19
|
+
# Computed attribute that combines first and last
|
41
20
|
def full_name
|
42
21
|
[first_name, last_name].join(' ')
|
43
22
|
end
|
@@ -8,10 +8,9 @@ Bundler.require(:default, ENV['RACK_ENV'])
|
|
8
8
|
# API field selection (a la GraphQL) - for querying and rendering
|
9
9
|
# API filtering extensions (to add "where clauses") in listings
|
10
10
|
# Views and partial rendering (for ActiveRecord models)
|
11
|
-
|
12
11
|
require 'praxis/plugins/mapper_plugin'
|
13
12
|
require 'praxis/mapper/active_model_compat'
|
14
|
-
|
13
|
+
# Want to take advantage of the pagination and sorting extensions as well
|
15
14
|
require 'praxis/plugins/pagination_plugin'
|
16
15
|
|
17
16
|
# Start the sqlite DB
|
@@ -3,7 +3,7 @@ Praxis::Application.configure do |application|
|
|
3
3
|
|
4
4
|
# Configure the Mapper plugin (if we want to use all the filtering/field_selection extensions)
|
5
5
|
application.bootloader.use Praxis::Plugins::MapperPlugin
|
6
|
-
#
|
6
|
+
# Configure the Pagination plugin (if we want to use all the pagination/ordering extensions)
|
7
7
|
application.bootloader.use Praxis::Plugins::PaginationPlugin, {
|
8
8
|
# max_items: 500, # Unlimited by default,
|
9
9
|
# default_page_size: 100,
|
@@ -33,6 +33,7 @@ Praxis::Application.configure do |application|
|
|
33
33
|
# map :models, 'models/**/*'
|
34
34
|
# map :responses, '**/responses/**/*'
|
35
35
|
# map :exceptions, '**/exceptions/**/*'
|
36
|
+
# map :concerns, '**/concerns/**/*'
|
36
37
|
# map :resources, '**/resources/**/*'
|
37
38
|
# map :controllers, '**/controllers/**/*'
|
38
39
|
# end
|
data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
class CreateUsersTable < ActiveRecord::Migration[5.2]
|
4
4
|
def change
|
5
5
|
create_table :users do |table|
|
6
|
-
table.column :uuid, :
|
7
|
-
table.column :first_name, :string
|
6
|
+
table.column :uuid, :string, null: false
|
7
|
+
table.column :first_name, :string, null: false
|
8
8
|
table.column :last_name, :string
|
9
|
+
table.column :email, :string
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
@@ -4,7 +4,6 @@ module V1
|
|
4
4
|
module Endpoints
|
5
5
|
class Users
|
6
6
|
include Praxis::EndpointDefinition
|
7
|
-
# include AuthenticatedEndpoint
|
8
7
|
|
9
8
|
media_type MediaTypes::User
|
10
9
|
version '1'
|
@@ -18,15 +17,16 @@ module V1
|
|
18
17
|
attribute :fields, Praxis::Types::FieldSelector.for(MediaTypes::User),
|
19
18
|
description: 'Fields with which to render the result.'
|
20
19
|
attribute :filters, Praxis::Types::FilteringParams.for(MediaTypes::User) do
|
21
|
-
filter '
|
20
|
+
filter 'uuid', using: ['=', '!=']
|
22
21
|
filter 'first_name', using: ['=', '!='], fuzzy: true
|
23
22
|
filter 'last_name', using: ['=', '!='], fuzzy: true
|
23
|
+
filter 'email', using: ['=', '!=']
|
24
24
|
end
|
25
25
|
attribute :pagination, Praxis::Types::PaginationParams.for(MediaTypes::User) do
|
26
|
-
by_fields :
|
26
|
+
by_fields :uuid, :first_name, :last_name
|
27
27
|
end
|
28
28
|
attribute :order, Praxis::Extensions::Pagination::OrderingParams.for(MediaTypes::User) do
|
29
|
-
by_fields :
|
29
|
+
by_fields :uuid, :last_name, :first_name
|
30
30
|
end
|
31
31
|
end
|
32
32
|
response :ok, media_type: Praxis::Collection.of(MediaTypes::User)
|
@@ -9,16 +9,11 @@ module V1
|
|
9
9
|
description 'A user in the system'
|
10
10
|
|
11
11
|
attributes do
|
12
|
-
attribute :
|
12
|
+
attribute :id, Integer
|
13
13
|
attribute :uuid, String
|
14
14
|
attribute :email, String
|
15
15
|
attribute :first_name, String
|
16
16
|
attribute :last_name, String
|
17
|
-
attribute :state, String, values: %i[pending active]
|
18
|
-
end
|
19
|
-
|
20
|
-
default_fieldset do
|
21
|
-
attribute :uid
|
22
17
|
end
|
23
18
|
end
|
24
19
|
end
|
@@ -5,8 +5,9 @@ class DatabaseHelper
|
|
5
5
|
# This does the job for an example seeder
|
6
6
|
def self.seed!
|
7
7
|
user_data = [
|
8
|
-
{id: 11, first_name: 'Peter', last_name: 'Praxis', uuid: 'deadbeef'},
|
9
|
-
{id: 12, first_name: 'Alice', last_name: 'Trellis', uuid: 'beefdead'}
|
8
|
+
{id: 11, first_name: 'Peter', last_name: 'Praxis', uuid: 'deadbeef', email: 'peter@pan.com'},
|
9
|
+
{id: 12, first_name: 'Alice', last_name: 'Trellis', uuid: 'beefdead', email: 'alice@wonderland.com'},
|
10
|
+
{id: 13, first_name: 'Wellington', last_name: 'Lofty', uuid: 'beefbeef', email: 'well@lofty.com'},
|
10
11
|
]
|
11
12
|
(100..199).each do |i|
|
12
13
|
user_data.push id: i, first_name: "User-#{i}", last_name: "Last-#{i}", uuid: SecureRandom.hex(16).to_s
|
@@ -14,5 +15,6 @@ class DatabaseHelper
|
|
14
15
|
user_data.each_with_index do |data, i|
|
15
16
|
::User.create(**data)
|
16
17
|
end
|
18
|
+
puts "Database seeded."
|
17
19
|
end
|
18
20
|
end
|
@@ -11,10 +11,10 @@ rescue => e
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Migrate and seed the DB (only an empty in-memory DB)
|
14
|
-
|
14
|
+
|
15
15
|
ActiveRecord::Migration.verbose = false # ?? does not seem to work like this
|
16
16
|
ActiveRecord::Tasks::DatabaseTasks.migrate
|
17
|
-
|
17
|
+
require_relative '../db/seeds.rb'
|
18
18
|
|
19
19
|
RSpec.configure do |config|
|
20
20
|
config.include Rack::Test::Methods
|
@@ -13,7 +13,7 @@ describe V1::Controllers::Users do
|
|
13
13
|
|
14
14
|
context 'index' do
|
15
15
|
let(:filters_q) { '' }
|
16
|
-
let(:fields_q) { '
|
16
|
+
let(:fields_q) { 'id' }
|
17
17
|
let(:query_string) do
|
18
18
|
"filters=#{CGI.escape(filters_q)}&fields=#{CGI.escape(fields_q)}"
|
19
19
|
end
|
@@ -30,7 +30,7 @@ describe V1::Controllers::Users do
|
|
30
30
|
it 'returns only peter' do
|
31
31
|
expect(parsed_body.size).to eq(1)
|
32
32
|
# Peter has id 11 from our seeds
|
33
|
-
expect(parsed_body.map{|u| u[:
|
33
|
+
expect(parsed_body.map{|u| u[:id]}).to eq([11])
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= version_module %>
|
4
|
+
module Endpoints
|
5
|
+
class <%= plural_class %>
|
6
|
+
include Praxis::EndpointDefinition
|
7
|
+
|
8
|
+
media_type MediaTypes::<%= singular_class %>
|
9
|
+
version '<%= version %>'
|
10
|
+
|
11
|
+
description 'Praxis-generated endpoint for managing <%= plural_class %>'
|
12
|
+
|
13
|
+
<%- if action_enabled?(:index) -%>
|
14
|
+
action :index do
|
15
|
+
description 'List <%= plural_class %>'
|
16
|
+
routing { get '' }
|
17
|
+
params do
|
18
|
+
attribute :fields, Praxis::Types::FieldSelector.for(MediaTypes::<%= singular_class %>),
|
19
|
+
description: 'Fields with which to render the result.'
|
20
|
+
<%- if !pagination_enabled? -%>
|
21
|
+
=begin
|
22
|
+
# You can use pagination/ordering by enabling the PaginationPlugin, and uncommenting these lines
|
23
|
+
<%- end -%>
|
24
|
+
attribute :pagination, Praxis::Types::PaginationParams.for(MediaTypes::<%= singular_class %>)
|
25
|
+
attribute :order, Praxis::Extensions::Pagination::OrderingParams.for(MediaTypes::<%= singular_class %>)
|
26
|
+
<%- if !pagination_enabled? -%>
|
27
|
+
=end
|
28
|
+
<%- end -%>
|
29
|
+
# # Filter by attributes. Add an allowed filter per line, with the allowed operators to use
|
30
|
+
# # Also, remember to add a mapping for each in `filters_mapping` method of Resources::<%= singular_class %> class
|
31
|
+
# attribute :filters, Praxis::Types::FilteringParams.for(MediaTypes::<%= singular_class %>) do
|
32
|
+
# filter 'first_name', using: ['=', '!='], fuzzy: true
|
33
|
+
# end
|
34
|
+
end
|
35
|
+
response :ok, media_type: Praxis::Collection.of(MediaTypes::<%= singular_class %>)
|
36
|
+
end
|
37
|
+
<%- end -%>
|
38
|
+
|
39
|
+
<%- if action_enabled?(:index) -%>
|
40
|
+
action :show do
|
41
|
+
description 'Retrieve details for a specific <%= singular_class %>'
|
42
|
+
routing { get '/:id' }
|
43
|
+
params do
|
44
|
+
attribute :id, required: true
|
45
|
+
attribute :fields, Praxis::Types::FieldSelector.for(MediaTypes::<%= singular_class %>),
|
46
|
+
description: 'Fields with which to render the result.'
|
47
|
+
end
|
48
|
+
response :ok
|
49
|
+
response :not_found
|
50
|
+
end
|
51
|
+
<%- end -%>
|
52
|
+
|
53
|
+
<%- if action_enabled?(:create) -%>
|
54
|
+
action :create do
|
55
|
+
description 'Create a new <%= singular_class %>'
|
56
|
+
routing { post '' }
|
57
|
+
payload reference: MediaTypes::<%= singular_class %> do
|
58
|
+
# List the attributes you accept from the one existing in the <%= singular_class %> Mediatype
|
59
|
+
# and/or fully define any other ones you allow at creation time
|
60
|
+
# attribute :name
|
61
|
+
end
|
62
|
+
response :created
|
63
|
+
response :bad_request
|
64
|
+
end
|
65
|
+
<%- end -%>
|
66
|
+
|
67
|
+
<%- if action_enabled?(:update) -%>
|
68
|
+
action :update do
|
69
|
+
description 'Update one or more attributes of an existing <%= singular_class %>'
|
70
|
+
routing { patch '/:id' }
|
71
|
+
params do
|
72
|
+
attribute :id, required: true
|
73
|
+
end
|
74
|
+
payload reference: MediaTypes::<%= singular_class %> do
|
75
|
+
# List the attributes you accept from the one existing in the <%= singular_class %> Mediatype
|
76
|
+
# and/or fully define any other ones you allow to change
|
77
|
+
# attribute :name
|
78
|
+
end
|
79
|
+
response :no_content
|
80
|
+
response :bad_request
|
81
|
+
end
|
82
|
+
<%- end -%>
|
83
|
+
|
84
|
+
<%- if action_enabled?(:update) -%>
|
85
|
+
action :delete do
|
86
|
+
description 'Deletes a <%= singular_class %>'
|
87
|
+
routing { delete '/:id' }
|
88
|
+
params do
|
89
|
+
attribute :id, required: true
|
90
|
+
end
|
91
|
+
response :no_content
|
92
|
+
response :not_found
|
93
|
+
end
|
94
|
+
<%- end -%>
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= version_module %>
|
4
|
+
module MediaTypes
|
5
|
+
class <%= singular_class %> < Praxis::MediaType
|
6
|
+
identifier 'application/json'
|
7
|
+
|
8
|
+
domain_model '<%= version_module %>::Resources::<%= singular_class %>'
|
9
|
+
description 'Structural definition of a <%= singular_class %>'
|
10
|
+
|
11
|
+
attributes do
|
12
|
+
attribute :id, Integer, description: '<%= singular_class %> identifier'
|
13
|
+
# <INSERT MORE ATTRIBUTES HERE>
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= version_module %>
|
4
|
+
module Controllers
|
5
|
+
class <%= plural_class %>
|
6
|
+
include Praxis::Controller
|
7
|
+
|
8
|
+
implements Endpoints::<%= plural_class %>
|
9
|
+
|
10
|
+
<%- if action_enabled?(:index) -%>
|
11
|
+
# Retrieve all <%= plural_class %> with the right necessary associations
|
12
|
+
# and render them appropriately with the requested field selection
|
13
|
+
def index
|
14
|
+
objects = build_query(model_class).all
|
15
|
+
display(objects)
|
16
|
+
end
|
17
|
+
<%- end -%>
|
18
|
+
|
19
|
+
<%- if action_enabled?(:show) -%>
|
20
|
+
# Retrieve a single <%= singular_class %> with the right necessary associations
|
21
|
+
# and render them appropriately with the requested field selection
|
22
|
+
def show(id:, **_args)
|
23
|
+
model = build_query(model_class.where(id: id)).first
|
24
|
+
return Praxis::Responses::NotFound.new if model.nil?
|
25
|
+
|
26
|
+
display(model)
|
27
|
+
end
|
28
|
+
<%- end -%>
|
29
|
+
|
30
|
+
<%- if action_enabled?(:create) -%>
|
31
|
+
# Creates a new <%= singular_class %>
|
32
|
+
def create
|
33
|
+
# A good pattern is to call the same name method on the corresponding resource,
|
34
|
+
# passing the incoming payload, or massaging it first
|
35
|
+
created_resource = Resources::<%= singular_class%>.create(request.payload)
|
36
|
+
|
37
|
+
# Respond with a created if it successfully finished
|
38
|
+
Praxis::Responses::Created.new(location: created_resource.href)
|
39
|
+
end
|
40
|
+
<%- end -%>
|
41
|
+
|
42
|
+
<%- if action_enabled?(:update) -%>
|
43
|
+
# Updates some of the information of a <%= singular_class %>
|
44
|
+
def update(id:)
|
45
|
+
# A good pattern is to call the same name method on the corresponding resource,
|
46
|
+
# passing the incoming id and payload (or massaging it first)
|
47
|
+
updated_resource = Resources::<%= singular_class %>.update(
|
48
|
+
id: id,
|
49
|
+
payload: request.payload,
|
50
|
+
)
|
51
|
+
return Praxis::Responses::NotFound.new unless updated_resource
|
52
|
+
|
53
|
+
Praxis::Responses::NoContent.new
|
54
|
+
end
|
55
|
+
<%- end -%>
|
56
|
+
|
57
|
+
<%- if action_enabled?(:delete) -%>
|
58
|
+
# Deletes an existing <%= singular_class %>
|
59
|
+
def delete(id:)
|
60
|
+
# A good pattern is to call the same name method on the corresponding resource,
|
61
|
+
# maybe passing the already loaded model
|
62
|
+
deleted_resource = Resources::<%= singular_class %>.delete(
|
63
|
+
id: id
|
64
|
+
)
|
65
|
+
return Praxis::Responses::NotFound.new unless deleted_resource
|
66
|
+
|
67
|
+
Praxis::Responses::NoContent.new
|
68
|
+
end
|
69
|
+
<%- end -%>
|
70
|
+
|
71
|
+
# Use the model class as the base query but you might want to change that
|
72
|
+
def model_class
|
73
|
+
::<%= singular_class %> #Change it to the appropriate DB model class
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= version_module %>
|
4
|
+
module Resources
|
5
|
+
class Base < Praxis::Mapper::Resource
|
6
|
+
# Base for all <%= version_module %> resources.
|
7
|
+
# Resources withing a single version should have resource mappings separate from other versions
|
8
|
+
# and the Mapper::Resource will appropriately maintain different model_maps for each Base classes
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= version_module %>
|
4
|
+
module Resources
|
5
|
+
class <%= singular_class %> < Base
|
6
|
+
model ::<%= singular_class %> # Change it if it maps to a different DB model class
|
7
|
+
|
8
|
+
# Define the name mapping from API filter params, to model attribute/associations
|
9
|
+
# when they aren't 1:1
|
10
|
+
# filters_mapping(
|
11
|
+
# 'name': 'name',
|
12
|
+
# 'label': 'association.label_name'
|
13
|
+
# )
|
14
|
+
|
15
|
+
# Add dependencies for resource attributes to other attributes and/or model associations
|
16
|
+
# property :href, dependencies: %i[id]
|
17
|
+
|
18
|
+
<%- if action_enabled?(:create) -%>
|
19
|
+
def self.create(payload)
|
20
|
+
# Assuming the API field names directly map the the model attributes. Massage if appropriate.
|
21
|
+
self.new(model.create(*payload.to_h))
|
22
|
+
end
|
23
|
+
<%- end -%>
|
24
|
+
|
25
|
+
<%- if action_enabled?(:update) -%>
|
26
|
+
def self.update(id:, payload:)
|
27
|
+
record = model.find_by(id: id)
|
28
|
+
return nil unless record
|
29
|
+
# Assuming the API field names directly map the the model attributes. Massage if appropriate.
|
30
|
+
record.update(*payload.to_h)
|
31
|
+
self.new(record)
|
32
|
+
end
|
33
|
+
<%- end -%>
|
34
|
+
|
35
|
+
<%- if action_enabled?(:delete) -%>
|
36
|
+
def self.delete(id:)
|
37
|
+
record = model.find_by(id: id)
|
38
|
+
return nil unless record
|
39
|
+
record.destroy
|
40
|
+
self.new(record)
|
41
|
+
end
|
42
|
+
<%- end -%>
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|