praxis 2.0.pre.10 → 2.0.pre.11
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/.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
|