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
@@ -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
|