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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7bbd374311046cf8d12c68d564382de6cbfd0c1032c45bbc8c1ac00d7a02e68a
|
4
|
+
data.tar.gz: e33676f45266facdbcae595c2c05ca94ae8dfa34134aa9e4113e5a5d9d729052
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5db86e95bd0b723560036435ded99b893fd92bd55f20dc4b49e326d96e7c9422549124ba986eb3248ff746d6bf928a60e6677b854c8761a34c026c7430a578bb
|
7
|
+
data.tar.gz: 39677f252617f79d3d054fd2286562d3355310d8161096c7c73294d2b242cee4ebdf749b462d7f1c5261fc487e80ba473dcd258dcb77a510c47db7e698275cad
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 2.0.pre.11
|
6
|
+
|
7
|
+
- Remove MapperPlugin's `set_selectors` (made `selector_generator` lazy instead), and ensure it includes the rendering extensions to the Controllers. Less things to configure if you opt into the Mapper way.
|
8
|
+
- Built scaffolding generator for quickly creating a new API endpoint in the praxis binary (it builds endpoint+mediatype+controller+resource at one, with useful base code and comments)
|
9
|
+
- Dropped support for Ruby 2.4 and 2.5 as some of the newest dependent gems are dropping it as well.
|
10
|
+
- Simplify filters_mapping definition, by not requiring to define same-name mappings if the underlying model has an attribute with the same exact name. i.e., a `name: :name` entry is not necessary if the model has a `:name` attribute.
|
11
|
+
|
12
|
+
## 2.0.pre.10
|
13
|
+
|
5
14
|
- Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
|
6
15
|
- Remove all deprecated features (and raise error describing it's not supported yet)
|
7
16
|
- Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
|
data/bin/praxis
CHANGED
@@ -46,7 +46,7 @@ class PraxisGenerator < Thor
|
|
46
46
|
def routes
|
47
47
|
end
|
48
48
|
|
49
|
-
desc "docs [generate|browser|package]",
|
49
|
+
desc "docs [generate|browser|package]", <<~EOF
|
50
50
|
Generates API documentation and a Web App to inspect it
|
51
51
|
generate - Generates the JSON docs
|
52
52
|
browser - (default) Generates JSON docs, and automatically starts a Web app to browse them.
|
@@ -81,7 +81,64 @@ class PraxisGenerator < Thor
|
|
81
81
|
gen = ::PraxisGen::Example.new([app_name])
|
82
82
|
gen.destination_root = app_name
|
83
83
|
gen.invoke(:example)
|
84
|
-
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc_for "g COLLECTION_NAME", ::PraxisGen::Scaffold, :g
|
87
|
+
# Cannot use the argument below or it will apply to all commands (the action in the class has it)
|
88
|
+
# argument :collection_name, required: false
|
89
|
+
# The options, however, since they're optional are fine (But need to be duplicated from the class :( )
|
90
|
+
option :version, required: false, default: '1',
|
91
|
+
desc: 'Version string for the API endpoint. This also dictates the directory structure (i.e., v1/endpoints/...))'
|
92
|
+
option :design, type: :boolean, default: true,
|
93
|
+
desc: 'Include the Endpoint and MediaType files for the collection'
|
94
|
+
option :implementation, type: :boolean, default: true,
|
95
|
+
desc: 'Include the Controller and (possibly the) Resource files for the collection (see --no-resource)'
|
96
|
+
option :resource, type: :boolean, default: true,
|
97
|
+
desc: 'Disable (or enable) the creation of the Resource files when generating implementation'
|
98
|
+
option :model, type: :string, enum: ['activerecord','sequel'],
|
99
|
+
desc: 'It also generates a model for the given ORM. An empty --model flag will default to activerecord'
|
100
|
+
option :actions, type: :string, default: 'crud', enum: ['cr','cru','crud','u','ud','d'],
|
101
|
+
desc: 'Specifies the actions to generate for the API. cr=create, u=update, d=delete. Index and show actions are always generated'
|
102
|
+
def g(*args)
|
103
|
+
# Because we cannot share the :collection_name argument, we need to do this check here, before
|
104
|
+
# we "parse" it and pass it to the g command
|
105
|
+
unless args.size == 1
|
106
|
+
::PraxisGen::Scaffold.command_help(shell,:g)
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
110
|
+
collection_name,_ = args
|
111
|
+
::PraxisGen::Scaffold.new([collection_name],options).invoke(:g)
|
112
|
+
if options[:model]
|
113
|
+
# Make it easy to be able to both enable or not enable the creation of the model, by passing --model=...
|
114
|
+
# but also make it easy so that if there is no value for it, it default to activerecord
|
115
|
+
opts = {orm: options[:model] }
|
116
|
+
opts[:orm] = 'activerecord' if opts[:orm] == 'model' # value is model param passed by no value
|
117
|
+
::PraxisGen::Model.new([collection_name.singularize],opts).invoke(:g)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Initially, the idea was to build some quick model generator, but I think it's better to keep it
|
122
|
+
# simple and just use the scaffold generator with `--no-implementation --no-design --model` instead
|
123
|
+
# Left here in case we want to rescue it
|
124
|
+
# desc_for "gmodel MODEL_NAME", ::PraxisGen::Model, :g
|
125
|
+
# # Cannot use the argument below or it will apply to all commands (the action in the class has it)
|
126
|
+
# # argument :collection_name, required: false
|
127
|
+
# # The options, however, since they're optional are fine (But need to be duplicated from the class :( )
|
128
|
+
# option :orm, required: false, default: 'activerecord', enum: ['activerecord','sequel'],
|
129
|
+
# desc: 'Type of ORM model to create.'
|
130
|
+
# def gmodel(*args)
|
131
|
+
# # Because we cannot share the :collection_name argument, we need to do this check here, before
|
132
|
+
# # we "parse" it and pass it to the g command
|
133
|
+
# unless args.size == 1
|
134
|
+
# ::PraxisGen::Model.command_help(shell,:g)
|
135
|
+
# exit 1
|
136
|
+
# end
|
137
|
+
|
138
|
+
# model_name,_ = args
|
139
|
+
# ::PraxisGen::Model.new([model_name],options).invoke(:g)
|
140
|
+
# end
|
141
|
+
|
85
142
|
end
|
86
143
|
|
87
144
|
PraxisGenerator.start(ARGV)
|
@@ -244,7 +244,7 @@ module Praxis
|
|
244
244
|
resources_by_version.keys.each do |version|
|
245
245
|
FileUtils.mkdir_p @doc_root_dir + '/' + version
|
246
246
|
end
|
247
|
-
FileUtils.mkdir_p @doc_root_dir + '/unversioned'
|
247
|
+
FileUtils.mkdir_p @doc_root_dir + '/unversioned' if resources_by_version.keys.include?('n/a')
|
248
248
|
end
|
249
249
|
|
250
250
|
def normalize_media_types( mtis )
|
@@ -5,27 +5,43 @@ module Praxis
|
|
5
5
|
module AttributeFiltering
|
6
6
|
ALIAS_TABLE_PREFIX = ''
|
7
7
|
require_relative 'active_record_patches'
|
8
|
+
# Helper class that can present an SqlLiteral string which we have already quoted
|
9
|
+
# ... but! that can properly provide a "to_sym" that has the value unquoted
|
10
|
+
# This is necessary as (the latest AR code):
|
11
|
+
# * does not carry over "references" in joins if they are not SqlLiterals
|
12
|
+
# * but, at the same time, it indexes the references using the .to_sym value (which is really expected to be the normal string, without quotes)
|
13
|
+
# If we pass a normal SqlLiteral, instead of our wrapper, without quoting the table, the current AR code will never quote it to form the
|
14
|
+
# SQL string, as it's already a literal...so our "/" type separators as names won't work without quoting.
|
15
|
+
class QuasiSqlLiteral < Arel::Nodes::SqlLiteral
|
16
|
+
def initialize(quoted:, symbolized:)
|
17
|
+
@symbolized = symbolized
|
18
|
+
super(quoted)
|
19
|
+
end
|
20
|
+
def to_sym
|
21
|
+
@symbolized
|
22
|
+
end
|
23
|
+
end
|
8
24
|
|
9
25
|
class ActiveRecordFilterQueryBuilder
|
10
|
-
attr_reader :query, :model, :
|
26
|
+
attr_reader :query, :model, :filters_map
|
11
27
|
|
12
28
|
# Base query to build upon
|
13
29
|
def initialize(query: , model:, filters_map:, debug: false)
|
14
30
|
@query = query
|
15
31
|
@model = model
|
16
|
-
@
|
32
|
+
@filters_map = filters_map
|
17
33
|
@logger = debug ? Logger.new(STDOUT) : nil
|
18
34
|
end
|
19
35
|
|
20
|
-
def
|
21
|
-
@logger
|
36
|
+
def debug_query(msg, query)
|
37
|
+
@logger.info(msg + query.to_sql) if @logger
|
22
38
|
end
|
23
39
|
|
24
40
|
def generate(filters)
|
25
41
|
# Resolve the names and values first, based on filters_map
|
26
42
|
root_node = _convert_to_treenode(filters)
|
27
43
|
craft_filter_query(root_node, for_model: @model)
|
28
|
-
|
44
|
+
debug_query("SQL due to filters: ", @query.all)
|
29
45
|
@query
|
30
46
|
end
|
31
47
|
|
@@ -45,13 +61,30 @@ module Praxis
|
|
45
61
|
|
46
62
|
private
|
47
63
|
|
64
|
+
def _mapped_filter(name)
|
65
|
+
target = @filters_map[name]
|
66
|
+
unless target
|
67
|
+
if @model.attribute_names.include?(name.to_s)
|
68
|
+
# Cache it in the filters mapping (to avoid later lookups), and return it.
|
69
|
+
@filters_map[name] = name
|
70
|
+
target = name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return target
|
74
|
+
end
|
75
|
+
|
48
76
|
# Resolve and convert from filters, to a more manageable and param-type-independent structure
|
49
77
|
def _convert_to_treenode(filters)
|
50
78
|
# Resolve the names and values first, based on filters_map
|
51
79
|
resolved_array = []
|
52
80
|
filters.parsed_array.each do |filter|
|
53
|
-
mapped_value =
|
54
|
-
|
81
|
+
mapped_value = _mapped_filter(filter[:name])
|
82
|
+
unless mapped_value
|
83
|
+
msg = "Filtering by #{filter[:name]} is not allowed. No implementation mapping defined for it has been found \
|
84
|
+
and there is not a model attribute with this name either.\n" \
|
85
|
+
"Please add a mapping for #{filter[:name]} in the `filters_mapping` method of the appropriate Resource class"
|
86
|
+
raise msg
|
87
|
+
end
|
55
88
|
bindings_array = \
|
56
89
|
if mapped_value.is_a?(Proc)
|
57
90
|
result = mapped_value.call(filter)
|
@@ -85,7 +118,7 @@ module Praxis
|
|
85
118
|
end
|
86
119
|
|
87
120
|
def add_clause(column_prefix:, column_object:, op:, value:)
|
88
|
-
@query = @query.references(column_prefix) #Mark where clause referencing the appropriate alias
|
121
|
+
@query = @query.references(build_reference_value(column_prefix)) #Mark where clause referencing the appropriate alias
|
89
122
|
likeval = get_like_value(value)
|
90
123
|
case op
|
91
124
|
when '!' # name! means => name IS NOT NULL (and the incoming value is nil)
|
@@ -165,6 +198,22 @@ module Praxis
|
|
165
198
|
likeval
|
166
199
|
end
|
167
200
|
end
|
201
|
+
|
202
|
+
# The value that we need to stick in the references method is different in the latest Rails
|
203
|
+
maj, min, _ = ActiveRecord.gem_version.segments
|
204
|
+
if maj == 5 || (maj == 6 && min == 0)
|
205
|
+
# In AR 6 (and 6.0) the references are simple strings
|
206
|
+
def build_reference_value(column_prefix)
|
207
|
+
column_prefix
|
208
|
+
end
|
209
|
+
else
|
210
|
+
# The latest AR versions discard passing references to joins when they're not SqlLiterals ... so let's wrap it
|
211
|
+
# with our class, so that it is a literal (already quoted), but that can still provide the expected "symbol" without quotes
|
212
|
+
# so that our aliasing code can match it.
|
213
|
+
def build_reference_value(column_prefix)
|
214
|
+
QuasiSqlLiteral.new(quoted: query.connection.quote_table_name(column_prefix), symbolized: column_prefix.to_sym)
|
215
|
+
end
|
216
|
+
end
|
168
217
|
end
|
169
218
|
end
|
170
219
|
end
|
@@ -9,7 +9,7 @@ module Praxis
|
|
9
9
|
class << self
|
10
10
|
def for(definition)
|
11
11
|
Class.new(self) do
|
12
|
-
@
|
12
|
+
@filters_map = case definition
|
13
13
|
when Hash
|
14
14
|
definition
|
15
15
|
when Array
|
@@ -18,7 +18,7 @@ module Praxis
|
|
18
18
|
raise "Cannot use FilterQueryBuilder.of without passing an array or a hash (Got: #{definition.class.name})"
|
19
19
|
end
|
20
20
|
class << self
|
21
|
-
attr_reader :
|
21
|
+
attr_reader :filters_map
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -33,13 +33,18 @@ module Praxis
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# By default we'll simply use the incoming op and value, and will map
|
36
|
-
# the attribute based on what's on the `
|
36
|
+
# the attribute based on what's on the `filters_map` definition
|
37
37
|
def generate(filters)
|
38
38
|
raise "Not refactored yet!"
|
39
39
|
seen_associations = Set.new
|
40
40
|
filters.each do |(attr, spec)|
|
41
|
-
column_name =
|
42
|
-
|
41
|
+
column_name = _mapped_filter(attr)
|
42
|
+
unless column_name
|
43
|
+
msg = "Filtering by #{attr} is not allowed. No implementation mapping defined for it has been found \
|
44
|
+
and there is not a model attribute with this name either.\n" \
|
45
|
+
"Please add a mapping for #{attr} in the `filters_mapping` method of the appropriate Resource class"
|
46
|
+
raise msg
|
47
|
+
end
|
43
48
|
if column_name.is_a?(Proc)
|
44
49
|
bindings = column_name.call(spec)
|
45
50
|
# A hash of bindings, consisting of a key with column name and a value to the query value
|
@@ -64,9 +69,16 @@ module Praxis
|
|
64
69
|
add_clause(attr: column_name, op: op, value: value)
|
65
70
|
end
|
66
71
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
72
|
+
def _mapped_filter(name)
|
73
|
+
target = self.class.filters_map[name]
|
74
|
+
unless target
|
75
|
+
if @model.attribute_names.include?(name.to_s)
|
76
|
+
# Cache it in the filters mapping (to avoid later lookups), and return it.
|
77
|
+
self.class.filters_map[name] = name
|
78
|
+
target = name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return target
|
70
82
|
end
|
71
83
|
|
72
84
|
# Private to try to funnel all column names through `generate` that restricts
|
@@ -14,10 +14,9 @@ module Praxis
|
|
14
14
|
module Pagination
|
15
15
|
extend ActiveSupport::Concern
|
16
16
|
# This PaginatedController concern should be added to controllers that have actions that define the
|
17
|
-
# pagination and order parameters so that
|
18
|
-
#
|
19
|
-
# This
|
20
|
-
# can be easily applied to other chainable query proxies.
|
17
|
+
# pagination and order parameters so that one can call the domain model to craft the query
|
18
|
+
# `domain_model.craft_pagination_query(base_query, pagination: _pagination)`
|
19
|
+
# This will handle all the required logic for paginating, ordering and generating the Link and TotalCount headers.
|
21
20
|
#
|
22
21
|
# Here's a simple example on how to use it for a fake Items controller
|
23
22
|
# class Items < V1::Controllers::BaseController
|
@@ -29,7 +28,8 @@ module Praxis
|
|
29
28
|
#
|
30
29
|
# def index(filters: nil, pagination: nil, order: nil, **_args)
|
31
30
|
# items = current_user.items.all
|
32
|
-
#
|
31
|
+
# domain_model = self.media_type.domain_model
|
32
|
+
# items = domain_model.craft_pagination_query( query: items, pagination: _pagination)
|
33
33
|
#
|
34
34
|
# display(items)
|
35
35
|
# end
|
@@ -71,33 +71,6 @@ module Praxis
|
|
71
71
|
@_pagination = PaginationStruct.new(pagination[:paginator], pagination[:order])
|
72
72
|
end
|
73
73
|
|
74
|
-
# Main entrypoint: Handles all pagination pieces
|
75
|
-
# takes:
|
76
|
-
# * the query to build from and the table
|
77
|
-
# * the request (for link header generation)
|
78
|
-
# * requires the _pagination variable to be there (set by this module) to return the pagination struct
|
79
|
-
def _craft_pagination_query(query:, type: :active_record)
|
80
|
-
handler_klass = \
|
81
|
-
case type
|
82
|
-
when :active_record
|
83
|
-
ActiveRecordPaginationHandler
|
84
|
-
when :sequel
|
85
|
-
SequelPaginationHandler
|
86
|
-
else
|
87
|
-
raise "Attempting to use pagination but Active Record or Sequel gems found"
|
88
|
-
end
|
89
|
-
|
90
|
-
# Gather and save the count if required
|
91
|
-
if _pagination.paginator&.total_count
|
92
|
-
_pagination.total_count = handler_klass.count(query.dup)
|
93
|
-
end
|
94
|
-
|
95
|
-
query = handler_klass.order(query, _pagination.order)
|
96
|
-
# Maybe this is a class instance instead of a class method?...(of the appropriate AR/Sequel type)...
|
97
|
-
# self.class.paginate(query, table, _pagination)
|
98
|
-
handler_klass.paginate(query, _pagination)
|
99
|
-
end
|
100
|
-
|
101
74
|
def build_pagination_headers(pagination:, current_url:, current_query_params:)
|
102
75
|
links = if pagination.paginator.by
|
103
76
|
# We're assuming that the last element has a "symbol/string" field with the same name of the "by" pagination.
|
@@ -23,6 +23,10 @@ module Praxis
|
|
23
23
|
Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector
|
24
24
|
end
|
25
25
|
|
26
|
+
def _pagination_query_builder_class
|
27
|
+
Praxis::Extensions::Pagination::ActiveRecordPaginationHandler
|
28
|
+
end
|
29
|
+
|
26
30
|
def _praxis_associations
|
27
31
|
orig = self.reflections.clone
|
28
32
|
|
@@ -30,6 +30,7 @@ module Praxis::Mapper
|
|
30
30
|
end
|
31
31
|
|
32
32
|
@properties = self.superclass.properties.clone
|
33
|
+
@_filters_map = {}
|
33
34
|
end
|
34
35
|
|
35
36
|
end
|
@@ -197,7 +198,7 @@ module Praxis::Mapper
|
|
197
198
|
|
198
199
|
# TODO: this shouldn't be needed if we incorporate it with the properties of the mapper...
|
199
200
|
# ...maybe what this means is that we can change it for a better DSL in the resource?
|
200
|
-
def self.filters_mapping(definition)
|
201
|
+
def self.filters_mapping(definition={})
|
201
202
|
@_filters_map = \
|
202
203
|
case definition
|
203
204
|
when Hash
|
@@ -211,7 +212,9 @@ module Praxis::Mapper
|
|
211
212
|
|
212
213
|
def self.craft_filter_query(base_query, filters:) # rubocop:disable Metrics/AbcSize
|
213
214
|
if filters
|
214
|
-
|
215
|
+
unless @_filters_map
|
216
|
+
raise "To use API filtering, you must define the mapping of api-names to resource properties (using the `filters_mapping` method in #{self})"
|
217
|
+
end
|
215
218
|
debug = Praxis::Application.instance.config.mapper.debug_queries
|
216
219
|
base_query = model._filter_query_builder_class.new(query: base_query, model: model, filters_map: @_filters_map, debug: debug).generate(filters)
|
217
220
|
end
|
@@ -228,6 +231,19 @@ module Praxis::Mapper
|
|
228
231
|
base_query
|
229
232
|
end
|
230
233
|
|
234
|
+
def self.craft_pagination_query(base_query, pagination: ) # rubocop:disable Metrics/AbcSize
|
235
|
+
handler_klass = model._pagination_query_builder_class
|
236
|
+
return base_query unless (handler_klass && (pagination.paginator || pagination.order))
|
237
|
+
|
238
|
+
# Gather and save the count if required
|
239
|
+
if pagination.paginator&.total_count
|
240
|
+
pagination.total_count = handler_klass.count(base_query.dup)
|
241
|
+
end
|
242
|
+
|
243
|
+
base_query = handler_klass.order(base_query, pagination.order)
|
244
|
+
handler_klass.paginate(base_query, pagination)
|
245
|
+
end
|
246
|
+
|
231
247
|
def initialize(record)
|
232
248
|
@record = record
|
233
249
|
end
|
@@ -7,6 +7,9 @@ module Praxis::Mapper
|
|
7
7
|
|
8
8
|
included do
|
9
9
|
attr_accessor :_resource
|
10
|
+
class <<self
|
11
|
+
alias_method :find_by, :find # Easy way to be method compatible with AR
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
module ClassMethods
|
@@ -19,6 +22,10 @@ module Praxis::Mapper
|
|
19
22
|
Praxis::Extensions::FieldSelection::SequelQuerySelector
|
20
23
|
end
|
21
24
|
|
25
|
+
def _pagination_query_builder_class
|
26
|
+
Praxis::Extensions::Pagination::SequelPaginationHandler
|
27
|
+
end
|
28
|
+
|
22
29
|
def _praxis_associations
|
23
30
|
orig = self.association_reflections.clone
|
24
31
|
orig.each do |k,v|
|