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
@@ -1,10 +1,23 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
|
3
|
+
require 'praxis/extensions/field_selection'
|
4
|
+
|
3
5
|
module Praxis
|
4
6
|
module Plugins
|
5
7
|
module MapperPlugin
|
6
8
|
include Praxis::PluginConcern
|
7
9
|
|
10
|
+
# The Mapper plugin is an overarching set of things to include in your application
|
11
|
+
# when you want to use the rendring, field_selection, filtering (and potentially pagination) extensions
|
12
|
+
# To use the plugin, set it up like any other plugin by registering to the bootloader.
|
13
|
+
# Typically you'd do that in environment.rb, inside the `Praxis::Application.configure do |application|` block, by:
|
14
|
+
# application.bootloader.use Praxis::Plugins::MapperPlugin
|
15
|
+
#
|
16
|
+
# The plugin accepts only 1 configuration option thus far, which you can set inside the same block as:
|
17
|
+
# application.config.mapper.debug_queries = true
|
18
|
+
# when debug_queries is set to true, the system will output information about the expanded fields
|
19
|
+
# and associations that the system ihas calculated necessary to pull from the DB, based on the requested
|
20
|
+
# API fields, API filters and `property` dependencies defined in the domain models (i.e., resources)
|
8
21
|
class Plugin < Praxis::Plugin
|
9
22
|
include Singleton
|
10
23
|
|
@@ -28,17 +41,11 @@ module Praxis
|
|
28
41
|
extend ActiveSupport::Concern
|
29
42
|
|
30
43
|
included do
|
44
|
+
include Praxis::Extensions::Rendering
|
31
45
|
include Praxis::Extensions::FieldExpansion
|
32
46
|
end
|
33
47
|
|
34
|
-
def
|
35
|
-
return unless self.media_type.respond_to?(:domain_model) &&
|
36
|
-
self.media_type.domain_model < Praxis::Mapper::Resource
|
37
|
-
|
38
|
-
selector_generator.add(self.media_type.domain_model, self.expanded_fields)
|
39
|
-
end
|
40
|
-
|
41
|
-
def build_query(base_query, type: :active_record) # rubocop:disable Metrics/AbcSize
|
48
|
+
def build_query(base_query) # rubocop:disable Metrics/AbcSize
|
42
49
|
domain_model = self.media_type&.domain_model
|
43
50
|
raise "No domain model defined for #{self.name}. Cannot use the attribute filtering helpers without it" unless domain_model
|
44
51
|
|
@@ -47,18 +54,20 @@ module Praxis
|
|
47
54
|
base_query = domain_model.craft_filter_query( base_query , filters: filters )
|
48
55
|
# Handle field and nested field selection
|
49
56
|
base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors)
|
50
|
-
# handle pagination and ordering
|
51
|
-
base_query =
|
57
|
+
# handle pagination and ordering if the pagination extention is included
|
58
|
+
base_query = domain_model.craft_pagination_query(base_query, pagination: _pagination) if self.respond_to?(:_pagination)
|
52
59
|
|
53
60
|
base_query
|
54
61
|
end
|
55
62
|
|
56
63
|
def selector_generator
|
57
|
-
|
58
|
-
|
64
|
+
return unless self.media_type.respond_to?(:domain_model) &&
|
65
|
+
self.media_type.domain_model < Praxis::Mapper::Resource
|
59
66
|
|
67
|
+
@selector_generator ||= \
|
68
|
+
Praxis::Mapper::SelectorGenerator.new.add(self.media_type.domain_model, self.expanded_fields)
|
69
|
+
end
|
60
70
|
end
|
61
|
-
|
62
71
|
end
|
63
72
|
end
|
64
73
|
end
|
@@ -1,14 +1,45 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'praxis/extensions/pagination'
|
3
3
|
|
4
|
-
#
|
4
|
+
# The PaginationPlugin can be configured to take advantage of adding pagination and sorting to
|
5
|
+
# your DB queries.
|
6
|
+
# When combined with the MapperPlugin, there is no extra configuration that needs to be done for
|
7
|
+
# the system to appropriately identify the pagination and order parameters in the API, and translate
|
8
|
+
# that in to the appropriate queries to fetch.
|
9
|
+
#
|
10
|
+
# To use this plugin without the MapperPlugin (probably a rare case), one can apply the appropriate
|
11
|
+
# clauses onto a query, by directly calling (in the controller) the `craft_pagination_query` method
|
12
|
+
# of the domain_model associated to the controller's mediatype.
|
13
|
+
# For example, here's how you can manually use this extension in a fictitious users index action:
|
14
|
+
# def index
|
15
|
+
# base_query = User.all # Start by not excluding any user
|
16
|
+
# domain_model = self.media_type.domain_model
|
17
|
+
# objs = domain_model.craft_pagination_query(base_query, pagination: _pagination)
|
18
|
+
# display(objs)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# This plugin accepts configuration about the default behavior of pagination.
|
22
|
+
# Any of these configs can individually be overidden when defining each Pagination/Order parameters
|
23
|
+
# in any of the Endpoint actions.
|
24
|
+
#
|
5
25
|
# Example configuration for this plugin
|
6
26
|
# Praxis::Application.configure do |application|
|
7
27
|
# application.bootloader.use Praxis::Plugins::PaginationPlugin, {
|
28
|
+
# # The maximum number of results that a paginated response will ever allow
|
8
29
|
# max_items: 500, # Unlimited by default,
|
30
|
+
# # The default page size to use when no `items` is specified
|
9
31
|
# default_page_size: 100,
|
10
|
-
#
|
11
|
-
#
|
32
|
+
# # Disallows the use of the page type pagination mode when true (i.e., using 'page=' parameter)
|
33
|
+
# disallow_paging_by_default: true, # Default false
|
34
|
+
# # Disallows the use of the cursor type pagination mode when true (i.e., using 'by=' or 'from=' parameter)
|
35
|
+
# disallow_cursor_by_default: true, # Default false
|
36
|
+
# # The default mode params to use
|
37
|
+
# paging_default_mode: {by: :uuid}, # Default {by: :uid}
|
38
|
+
# # Weather or not to enforce that all requested sort fields are part of the media_type attributes
|
39
|
+
# # when false (not enforced) only the first field would be checked
|
40
|
+
# sorting: {
|
41
|
+
# enforce_all_fields: false # Default true
|
42
|
+
# }
|
12
43
|
# end
|
13
44
|
# end
|
14
45
|
#
|
@@ -39,7 +70,6 @@ module Praxis
|
|
39
70
|
attribute :paging_default_mode, Hash, default: Praxis::Types::PaginationParams.paging_default_mode
|
40
71
|
attribute :disallow_paging_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_paging_by_default
|
41
72
|
attribute :disallow_cursor_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_cursor_by_default
|
42
|
-
attribute :disallow_cursor_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_cursor_by_default
|
43
73
|
attribute :sorting do
|
44
74
|
attribute :enforce_all_fields, Attributor::Boolean, default: Praxis::Types::OrderingParams.enforce_all_fields
|
45
75
|
end
|
@@ -21,7 +21,10 @@ namespace :praxis do
|
|
21
21
|
trap('INT') { s.shutdown }
|
22
22
|
s.start
|
23
23
|
end
|
24
|
-
|
24
|
+
# If there is only 1 version we'll feature it and open the browser onto it
|
25
|
+
versions = Dir.children(root)
|
26
|
+
featured_version = (versions.size < 2) ? "#{versions.first}/" : ''
|
27
|
+
`open http://localhost:#{docs_port}/#{featured_version}`
|
25
28
|
wb.join
|
26
29
|
end
|
27
30
|
desc "Generate and package all OpenApi Docs into a zip, ready for a Web server (like S3...) to present it"
|
data/lib/praxis/version.rb
CHANGED
@@ -39,7 +39,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
39
39
|
instance
|
40
40
|
expect(instance.query).to eq(base_query)
|
41
41
|
expect(instance.model).to eq(base_model)
|
42
|
-
expect(instance.
|
42
|
+
expect(instance.filters_map).to eq(filters_map)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
context 'generate' do
|
@@ -57,7 +57,20 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
57
57
|
let(:filters_string) { 'category_uuid=deadbeef1' }
|
58
58
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1')
|
59
59
|
end
|
60
|
-
context '
|
60
|
+
context 'same-name filter mapping works' do
|
61
|
+
context 'even if ther was not a filter explicitly defined for it' do
|
62
|
+
let(:filters_string) { 'category_uuid=deadbeef1' }
|
63
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1')
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'but if it is a field that does not exist in the model' do
|
67
|
+
let(:filters_string) { 'nonexisting=valuehere' }
|
68
|
+
it 'it blows up with the right error' do
|
69
|
+
expect{subject}.to raise_error(/Filtering by nonexisting is not allowed/)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
context 'that maps to a different name' do
|
61
74
|
let(:filters_string) { 'name=Book1'}
|
62
75
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
63
76
|
end
|
@@ -29,7 +29,7 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
29
29
|
:id # We always load the primary keys
|
30
30
|
]
|
31
31
|
end
|
32
|
-
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields) }
|
32
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields).selectors }
|
33
33
|
let(:debug){ false }
|
34
34
|
|
35
35
|
subject(:selector) {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
@@ -64,7 +64,7 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
64
64
|
]
|
65
65
|
end
|
66
66
|
|
67
|
-
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields) }
|
67
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields).selectors }
|
68
68
|
subject {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
69
69
|
|
70
70
|
context 'generate' do
|
@@ -103,7 +103,7 @@ class ActiveBookResource < ActiveBaseResource
|
|
103
103
|
|
104
104
|
filters_mapping(
|
105
105
|
id: :id,
|
106
|
-
category_uuid: :category_uuid
|
106
|
+
# category_uuid: :category_uuid #NOTE: we do not need to define same-name-mappings if they exist
|
107
107
|
'fake_nested.name': 'simple_name',
|
108
108
|
'name': 'simple_name',
|
109
109
|
'name_is_not': lambda do |spec| # Silly way to use a proc, but good enough for testing
|
@@ -8,7 +8,7 @@ describe Praxis::Mapper::SelectorGenerator do
|
|
8
8
|
context '#add' do
|
9
9
|
let(:resource) { SimpleResource }
|
10
10
|
shared_examples 'a proper selector' do
|
11
|
-
it { expect(generator.add(resource, fields).dump).to be_deep_equal selectors }
|
11
|
+
it { expect(generator.add(resource, fields).selectors.dump).to be_deep_equal selectors }
|
12
12
|
end
|
13
13
|
|
14
14
|
context 'basic combos' do
|
data/tasks/thor/example.rb
CHANGED
@@ -33,16 +33,22 @@ module PraxisGen
|
|
33
33
|
puts
|
34
34
|
puts " cd #{app_name}"
|
35
35
|
puts " bundle"
|
36
|
-
puts " bundle exec rake db:
|
37
|
-
puts " bundle exec rackup
|
36
|
+
puts " bundle exec rake db:recreate # To create/migrate/seed the dev DB"
|
37
|
+
puts " bundle exec rackup # To start the web server"
|
38
38
|
puts
|
39
39
|
puts "From another terminal/app, use curl (or your favorite HTTP client) to retrieve data from the API"
|
40
40
|
puts " For example: "
|
41
|
-
puts " Get all users without filters or limit, and display only
|
42
|
-
puts " curl -H 'X-Api-Version: 1' http://localhost:9292/users
|
41
|
+
puts " Get all users without filters or limit, and display only id, and first_name fields"
|
42
|
+
puts " curl -G -H 'X-Api-Version: 1' http://localhost:9292/users \\"
|
43
|
+
puts " --data-urlencode \"fields=id,first_name\""
|
43
44
|
puts
|
44
|
-
puts " Get the last 5 users, ordered by
|
45
|
-
puts "
|
45
|
+
puts " Get the last 5 users, with last_names starting with \"L\" ordered by first_name (descending)"
|
46
|
+
puts " and display only id, first_name, last_name, and email fields"
|
47
|
+
puts " curl -G -H 'X-Api-Version: 1' http://localhost:9292/users \\"
|
48
|
+
puts " --data-urlencode \"filters=last_name=L*\" \\"
|
49
|
+
puts " --data-urlencode \"pagination=by=first_name,items=5\" \\"
|
50
|
+
puts " --data-urlencode \"order=-first_name\" \\"
|
51
|
+
puts " --data-urlencode \"fields=id,first_name,last_name,email\""
|
46
52
|
puts " (Note: To list all routes use: bundle exec rake praxis:routes)"
|
47
53
|
puts
|
48
54
|
nil
|
data/tasks/thor/model.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PraxisGen
|
4
|
+
class Model < Thor
|
5
|
+
require 'active_support/inflector'
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
File.dirname(__FILE__) + "/templates/generator/scaffold"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "gmodel", "Generates a skeleton model file under app/models for ActiveRecord or Sequel."
|
13
|
+
argument :model_name, required: true
|
14
|
+
option :orm, required: false, default: 'activerecord', enum: ['activerecord','sequel']
|
15
|
+
def g
|
16
|
+
#self.class.check_name(model_name)
|
17
|
+
template_file = \
|
18
|
+
if options[:orm] == 'activerecord'
|
19
|
+
'models/active_record.rb'
|
20
|
+
else
|
21
|
+
'models/sequel.rb'
|
22
|
+
end
|
23
|
+
puts "Generating Model for #{model_name}"
|
24
|
+
template template_file, "app/models/#{model_name}.rb"
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
# Helper functions (which are available in the ERB contexts)
|
28
|
+
no_commands do
|
29
|
+
def model_class
|
30
|
+
model_name.camelize
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: do we want the argument to be camelcase? or snake case?
|
35
|
+
def self.check_name(name)
|
36
|
+
sanitized = name.downcase.gsub(/[^a-z0-9_]/, '')
|
37
|
+
raise "Please use only downcase letters, numbers and underscores for the model" unless sanitized == name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PraxisGen
|
4
|
+
class Scaffold < Thor
|
5
|
+
require 'active_support/inflector'
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
attr_reader :actions_hash
|
9
|
+
|
10
|
+
def self.source_root
|
11
|
+
File.dirname(__FILE__) + "/templates/generator/scaffold"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "g","Generates an API design and implementation scaffold for managing a collection of <collection_name>"
|
15
|
+
argument :collection_name, required: true
|
16
|
+
option :version, required: false, default: '1',
|
17
|
+
desc: 'Version string for the API endpoint. This also dictates the directory structure (i.e., v1/endpoints/...))'
|
18
|
+
option :design, type: :boolean, default: true,
|
19
|
+
desc: 'Include the Endpoint and MediaType files for the collection'
|
20
|
+
option :implementation, type: :boolean, default: true,
|
21
|
+
desc: 'Include the Controller and (possibly the) Resource files for the collection (see --no-resource)'
|
22
|
+
option :resource, type: :boolean, default: true,
|
23
|
+
desc: 'Disable (or enable) the creation of the Resource files when generating implementation'
|
24
|
+
option :model, type: :string, enum: ['activerecord','sequel'],
|
25
|
+
desc: 'It also generates a model for the given ORM. An empty --model flag will default to activerecord'
|
26
|
+
option :actions, type: :string, default: 'crud', enum: ['cr','cru','crud','u','ud','d'],
|
27
|
+
desc: 'Specifies the actions to generate for the API. cr=create, u=update, d=delete. Index and show actions are always generated'
|
28
|
+
def g
|
29
|
+
self.class.check_name(collection_name)
|
30
|
+
@actions_hash = self.class.compose_actions_hash(options[:actions])
|
31
|
+
env_rb = Pathname.new(destination_root)+Pathname.new("config/environment.rb")
|
32
|
+
@pagination_plugin_found = File.open(env_rb).grep(/Praxis::Plugins::PaginationPlugin.*/).reject{|l| l.strip[0] == '#'}.present?
|
33
|
+
if options[:design]
|
34
|
+
say_status 'Design', "Generating scaffold for #{plural_class}", :blue
|
35
|
+
template 'design/media_types/item.rb', "design/#{version_dir}/media_types/#{collection_name.singularize}.rb"
|
36
|
+
template 'design/endpoints/collection.rb', "design/#{version_dir}/endpoints/#{collection_name}.rb"
|
37
|
+
end
|
38
|
+
if options[:implementation]
|
39
|
+
say_status 'Implement', "Generating scaffold for #{plural_class}", :blue
|
40
|
+
if options[:resource]
|
41
|
+
base_resource = Pathname.new(destination_root)+Pathname.new("app/#{version_dir}/resources/base.rb")
|
42
|
+
unless base_resource.exist?
|
43
|
+
# Copy an appropriate base resource for the version (resources within same version must share same base)
|
44
|
+
say_status "NOTE:",
|
45
|
+
"Creating a base resource file for resources to inherit from (at 'app/#{version_dir}/resources/base.rb')",
|
46
|
+
:yellow
|
47
|
+
say_status "",
|
48
|
+
"If you had already other resources in the app, change them to derive from this Base"
|
49
|
+
template 'implementation/resources/base.rb', "app/#{version_dir}/resources/base.rb"
|
50
|
+
end
|
51
|
+
template 'implementation/resources/item.rb', "app/#{version_dir}/resources/#{collection_name.singularize}.rb"
|
52
|
+
end
|
53
|
+
template 'implementation/controllers/collection.rb', "app/#{version_dir}/controllers/#{collection_name}.rb"
|
54
|
+
end
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Helper functions (which are available in the ERB contexts)
|
59
|
+
no_commands do
|
60
|
+
def plural_class
|
61
|
+
collection_name.camelize
|
62
|
+
end
|
63
|
+
|
64
|
+
def singular_class
|
65
|
+
collection_name.singularize.camelize
|
66
|
+
end
|
67
|
+
|
68
|
+
def version
|
69
|
+
options[:version]
|
70
|
+
end
|
71
|
+
|
72
|
+
def version_module
|
73
|
+
"V#{version}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def version_dir
|
77
|
+
version_module.camelize(:lower)
|
78
|
+
end
|
79
|
+
|
80
|
+
def action_enabled?(action)
|
81
|
+
@actions_hash[action.to_sym]
|
82
|
+
end
|
83
|
+
|
84
|
+
def pagination_enabled?
|
85
|
+
@pagination_plugin_found
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.compose_actions_hash(actions_opt)
|
90
|
+
required = { index: true, show: true }
|
91
|
+
case actions_opt
|
92
|
+
when nil
|
93
|
+
required
|
94
|
+
when 'cr'
|
95
|
+
required.merge(create: true)
|
96
|
+
when 'cru'
|
97
|
+
required.merge(create: true, update: true)
|
98
|
+
when 'crud'
|
99
|
+
required.merge(create: true, update: true, delete: true)
|
100
|
+
when 'u'
|
101
|
+
required.merge(update: true)
|
102
|
+
when 'ud'
|
103
|
+
required.merge(update: true, delete: true)
|
104
|
+
when 'd'
|
105
|
+
required.merge(delete: true)
|
106
|
+
else
|
107
|
+
raise "actions option does not support the string #{actions_opt}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.check_name(name)
|
112
|
+
sanitized = name.downcase.gsub(/[^a-z0-9_]/, '')
|
113
|
+
# TODO: bail or support CamelCase collections (for now only snake case)
|
114
|
+
raise "Please use only downcase letters, numbers and underscores for the collection" unless sanitized == name
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -18,6 +18,7 @@ Praxis::Application.configure do |application|
|
|
18
18
|
# map :models, 'models/**/*'
|
19
19
|
# map :responses, '**/responses/**/*'
|
20
20
|
# map :exceptions, '**/exceptions/**/*'
|
21
|
+
# map :concerns, '**/concerns/**/*'
|
21
22
|
# map :resources, '**/resources/**/*'
|
22
23
|
# map :controllers, '**/controllers/**/*'
|
23
24
|
# end
|
@@ -32,10 +32,17 @@ namespace :db do
|
|
32
32
|
puts "Database migrated."
|
33
33
|
end
|
34
34
|
|
35
|
+
desc 'Fully receate, migrate and seed the DB'
|
36
|
+
task :recreate do
|
37
|
+
Rake::Task['db:drop'].invoke rescue nil
|
38
|
+
Rake::Task['db:create'].invoke
|
39
|
+
Rake::Task['db:migrate'].invoke
|
40
|
+
Rake::Task['db:seed'].invoke
|
41
|
+
end
|
42
|
+
|
35
43
|
desc 'seed with example data'
|
36
44
|
task seed: 'praxis:environment' do
|
37
|
-
require_relative '
|
38
|
-
DatabaseHelper.seed!
|
45
|
+
require_relative 'db/seeds.rb'
|
39
46
|
end
|
40
47
|
|
41
48
|
desc 'drops current database'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module V1
|
2
|
+
module Concerns
|
3
|
+
module ControllerBase
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
# Controller concen that wraps an API with a transaction, and automatically rolls it back
|
6
|
+
# for non-2xx (or 3xx) responses
|
7
|
+
included do
|
8
|
+
around :action do |controller, callee|
|
9
|
+
begin
|
10
|
+
# TODO: Support Sequel as well
|
11
|
+
ActiveRecord::Base.transaction do
|
12
|
+
callee.call
|
13
|
+
res = controller.response
|
14
|
+
# Force a rollback for non 2xx or 3xx responses
|
15
|
+
raise ActiveRecord::Rollback unless res.status >= 200 && res.status < 400
|
16
|
+
end
|
17
|
+
rescue ActiveRecord::Rollback
|
18
|
+
# No need to do anything, let the responses flow normally
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|