praxis 2.0.pre.5 → 2.0.pre.6
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 +5 -5
- data/.rspec +0 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/Guardfile +2 -1
- data/Rakefile +1 -7
- data/TODO.md +28 -0
- data/lib/api_browser/package-lock.json +7110 -0
- data/lib/praxis.rb +6 -4
- data/lib/praxis/action_definition.rb +9 -16
- data/lib/praxis/application.rb +1 -2
- data/lib/praxis/bootloader_stages/routing.rb +2 -4
- data/lib/praxis/extensions/attribute_filtering.rb +2 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +148 -157
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +15 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +90 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +68 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +58 -0
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +35 -0
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +9 -12
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +3 -2
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +7 -9
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +6 -9
- data/lib/praxis/extensions/pagination.rb +130 -0
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +42 -0
- data/lib/praxis/extensions/pagination/header_generator.rb +70 -0
- data/lib/praxis/extensions/pagination/ordering_params.rb +234 -0
- data/lib/praxis/extensions/pagination/pagination_handler.rb +68 -0
- data/lib/praxis/extensions/pagination/pagination_params.rb +374 -0
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +45 -0
- data/lib/praxis/handlers/json.rb +2 -0
- data/lib/praxis/handlers/www_form.rb +5 -0
- data/lib/praxis/handlers/{xml.rb → xml-sample.rb} +6 -0
- data/lib/praxis/mapper/active_model_compat.rb +23 -5
- data/lib/praxis/mapper/resource.rb +16 -9
- data/lib/praxis/mapper/sequel_compat.rb +1 -0
- data/lib/praxis/media_type.rb +1 -56
- data/lib/praxis/plugins/mapper_plugin.rb +1 -1
- data/lib/praxis/plugins/pagination_plugin.rb +71 -0
- data/lib/praxis/resource_definition.rb +4 -12
- data/lib/praxis/route.rb +2 -4
- data/lib/praxis/routing_config.rb +4 -8
- data/lib/praxis/tasks/routes.rb +9 -14
- data/lib/praxis/validation_handler.rb +1 -2
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +2 -3
- data/spec/functional_spec.rb +9 -6
- data/spec/praxis/action_definition_spec.rb +4 -16
- data/spec/praxis/api_general_info_spec.rb +6 -6
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +304 -0
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +39 -0
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +34 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +6 -24
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +15 -11
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +4 -3
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +130 -0
- data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_active_model.rb +45 -2
- data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_sequel.rb +0 -0
- data/spec/praxis/media_type_spec.rb +5 -129
- data/spec/praxis/request_spec.rb +3 -22
- data/spec/praxis/resource_definition_spec.rb +1 -1
- data/spec/praxis/response_definition_spec.rb +1 -5
- data/spec/praxis/route_spec.rb +2 -9
- data/spec/praxis/routing_config_spec.rb +4 -13
- data/spec/praxis/types/multipart_array_spec.rb +4 -21
- data/spec/spec_app/config/environment.rb +0 -2
- data/spec/spec_app/design/api.rb +1 -1
- data/spec/spec_app/design/media_types/instance.rb +0 -8
- data/spec/spec_app/design/media_types/volume.rb +0 -12
- data/spec/spec_app/design/resources/instances.rb +1 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/support/spec_media_types.rb +0 -73
- metadata +35 -45
- data/spec/praxis/handlers/xml_spec.rb +0 -177
- data/spec/praxis/links_spec.rb +0 -68
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'praxis/extensions/attribute_filtering'
|
4
|
+
|
5
|
+
describe Praxis::Extensions::AttributeFiltering::FilterTreeNode do
|
6
|
+
|
7
|
+
let(:filters) do
|
8
|
+
[
|
9
|
+
{name: 'one', specs: { op: '>', value: 1}},
|
10
|
+
{name: 'one', specs: { op: '<', value: 10}},
|
11
|
+
{name: 'rel1.a1', specs: { op: '=', value: 1}},
|
12
|
+
{name: 'rel1.a2', specs: { op: '=', value: 2}},
|
13
|
+
{name: 'rel1.rel2.b1', specs: { op: '=', value: 11}},
|
14
|
+
{name: 'rel1.rel2.b2', specs: { op: '=', value: 12}}
|
15
|
+
]
|
16
|
+
end
|
17
|
+
context 'initialization' do
|
18
|
+
subject { described_class.new(filters) }
|
19
|
+
it 'holds the top conditions and the child in a TreeNode' do
|
20
|
+
expect(subject.path).to eq([])
|
21
|
+
expect(subject.conditions.size).to eq(2)
|
22
|
+
expect(subject.children.keys).to eq(['rel1'])
|
23
|
+
expect(subject.children['rel1']).to be_kind_of(described_class)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'recursively holds the conditions and the children of their children in a TreeNode' do
|
27
|
+
rel1 = subject.children['rel1']
|
28
|
+
expect(rel1.path).to eq(['rel1'])
|
29
|
+
expect(rel1.conditions.size).to eq(2)
|
30
|
+
expect(rel1.children.keys).to eq(['rel2'])
|
31
|
+
expect(rel1.children['rel2']).to be_kind_of(described_class)
|
32
|
+
|
33
|
+
rel1rel2 = rel1.children['rel2']
|
34
|
+
expect(rel1rel2.path).to eq(['rel1','rel2'])
|
35
|
+
expect(rel1rel2.conditions.size).to eq(2)
|
36
|
+
expect(rel1rel2.children.keys).to be_empty
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'praxis/extensions/attribute_filtering'
|
4
|
+
|
5
|
+
describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
6
|
+
|
7
|
+
context '.load' do
|
8
|
+
subject { described_class.load(filters_string) }
|
9
|
+
context 'parses for operator' do
|
10
|
+
described_class::AVAILABLE_OPERATORS.each do |op|
|
11
|
+
it "#{op}" do
|
12
|
+
str = "thename#{op}thevalue"
|
13
|
+
parsed = [{ name: :thename, op: op, value: 'thevalue'}]
|
14
|
+
expect(described_class.load(str).parsed_array).to eq(parsed)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context 'with all operators at once' do
|
19
|
+
let(:filters_string) { 'one=1&two!=2&three>=3&four<=4&five<5&six>6&seven!&eight!!'}
|
20
|
+
it do
|
21
|
+
expect(subject.parsed_array).to eq([
|
22
|
+
{ name: :one, op: '=', value: '1'},
|
23
|
+
{ name: :two, op: '!=', value: '2'},
|
24
|
+
{ name: :three, op: '>=', value: '3'},
|
25
|
+
{ name: :four, op: '<=', value: '4'},
|
26
|
+
{ name: :five, op: '<', value: '5'},
|
27
|
+
{ name: :six, op: '>', value: '6'},
|
28
|
+
{ name: :seven, op: '!', value: nil},
|
29
|
+
{ name: :eight, op: '!!', value: nil},
|
30
|
+
])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -47,15 +47,10 @@ describe Praxis::Extensions::FieldExpansion do
|
|
47
47
|
|
48
48
|
context 'and no fields provided' do
|
49
49
|
it 'returns the fields for the default view' do
|
50
|
-
expect(expansion).to eq({id: true, name: true
|
50
|
+
expect(expansion).to eq({id: true, name: true})
|
51
51
|
end
|
52
52
|
|
53
|
-
|
54
|
-
let(:view) { :link }
|
55
|
-
it 'expands the fields on the view' do
|
56
|
-
expect(expansion).to eq({id: true, name: true, href: true})
|
57
|
-
end
|
58
|
-
end
|
53
|
+
pending 'and a view'
|
59
54
|
end
|
60
55
|
|
61
56
|
context 'with a set of fields provided' do
|
@@ -65,15 +60,7 @@ describe Praxis::Extensions::FieldExpansion do
|
|
65
60
|
expect(expansion).to eq expected
|
66
61
|
end
|
67
62
|
|
68
|
-
|
69
|
-
let(:view) { :link }
|
70
|
-
let(:fields) { 'id,href' }
|
71
|
-
|
72
|
-
it 'returns the subset of fields that exist for the view' do
|
73
|
-
expected = {id: true, href: true }
|
74
|
-
expect(expansion).to eq expected
|
75
|
-
end
|
76
|
-
end
|
63
|
+
pending 'and a view'
|
77
64
|
end
|
78
65
|
end
|
79
66
|
|
@@ -81,15 +68,10 @@ describe Praxis::Extensions::FieldExpansion do
|
|
81
68
|
let(:test_attributes) { {view: true} }
|
82
69
|
|
83
70
|
it 'returns the fields for the default view' do
|
84
|
-
expect(expansion).to eq({id: true, name: true
|
71
|
+
expect(expansion).to eq({id: true, name: true})
|
85
72
|
end
|
86
73
|
|
87
|
-
|
88
|
-
let(:view) { :link }
|
89
|
-
it 'expands the fields on the view' do
|
90
|
-
expect(expansion).to eq({id: true, name: true, href: true})
|
91
|
-
end
|
92
|
-
end
|
74
|
+
pending 'and a view'
|
93
75
|
end
|
94
76
|
|
95
77
|
|
@@ -98,7 +80,7 @@ describe Praxis::Extensions::FieldExpansion do
|
|
98
80
|
let(:fields){ nil }
|
99
81
|
let(:view){ nil }
|
100
82
|
it 'ignores incoming parameters and expands for the default view' do
|
101
|
-
expect(expansion).to eq({id: true, name: true
|
83
|
+
expect(expansion).to eq({id: true, name: true})
|
102
84
|
end
|
103
85
|
end
|
104
86
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
require_relative 'support/spec_resources_active_model.rb'
|
3
|
+
require_relative '../support/spec_resources_active_model.rb'
|
4
4
|
|
5
5
|
describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
6
6
|
let(:selector_fields) do
|
@@ -30,8 +30,9 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
30
30
|
]
|
31
31
|
end
|
32
32
|
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields) }
|
33
|
+
let(:debug){ false }
|
33
34
|
|
34
|
-
subject(:selector) {described_class.new(query: query, selectors: selector_node) }
|
35
|
+
subject(:selector) {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
35
36
|
context '#generate with a mocked' do
|
36
37
|
let(:query) { double("Query") }
|
37
38
|
it 'calls the select columns for the top level, and includes the right association hashes' do
|
@@ -49,12 +50,15 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
49
50
|
expect(subject).to_not receive(:explain_query)
|
50
51
|
subject.generate
|
51
52
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
context 'when debug is enabled' do
|
54
|
+
let(:debug){ true }
|
55
|
+
it 'calls the explain method' do
|
56
|
+
expect(query).to receive(:select).and_return(query)
|
57
|
+
expect(query).to receive(:includes).and_return(query)
|
58
|
+
expect(subject).to receive(:explain_query)
|
59
|
+
subject.generate
|
60
|
+
end
|
61
|
+
end
|
58
62
|
end
|
59
63
|
|
60
64
|
context '#generate with a real AR model' do
|
@@ -79,14 +83,14 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
79
83
|
expect(includes_hash).to match(expected_includes)
|
80
84
|
# Also, make AR do the actual query to make sure everything is wired up correctly
|
81
85
|
result = final_query.to_a
|
82
|
-
expect(result.size).to be 2
|
86
|
+
expect(result.size).to be > 2 # We are using 2 but we've seeded more
|
83
87
|
book1 = result[0]
|
84
88
|
book2 = result[1]
|
85
89
|
expect(book1.author.id).to eq 11
|
86
90
|
expect(book1.author.books.size).to eq 1
|
87
91
|
expect(book1.author.books.map(&:simple_name)).to eq(['Book1'])
|
88
92
|
expect(book1.category.name).to eq 'cat1'
|
89
|
-
expect(book1.tags.map(&:name)).to match_array(['blue','red'])
|
93
|
+
expect(book1.tags.map(&:name)).to match_array(['blue','red','green'])
|
90
94
|
|
91
95
|
expect(book2.author.id).to eq 22
|
92
96
|
expect(book2.author.books.size).to eq 1
|
@@ -98,7 +102,7 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
98
102
|
it 'calls the explain debug method if enabled' do
|
99
103
|
suppress_output do
|
100
104
|
# Actually make it run all the way...but suppressing the output
|
101
|
-
subject.generate
|
105
|
+
subject.generate
|
102
106
|
end
|
103
107
|
end
|
104
108
|
end
|
@@ -37,7 +37,7 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
37
37
|
# Pay the price for creating and connecting only in this spec instead in spec helper
|
38
38
|
# this way all other specs do not need to be slower and it's a better TDD experience
|
39
39
|
|
40
|
-
require_relative 'support/spec_resources_sequel.rb'
|
40
|
+
require_relative '../support/spec_resources_sequel.rb'
|
41
41
|
|
42
42
|
let(:selector_fields) do
|
43
43
|
{
|
@@ -65,9 +65,10 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields) }
|
68
|
-
subject {described_class.new(query: query, selectors: selector_node) }
|
68
|
+
subject {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
69
69
|
|
70
70
|
context 'generate' do
|
71
|
+
let(:debug) { false }
|
71
72
|
context 'using the real models and DB' do
|
72
73
|
let(:query) { SequelSimpleModel }
|
73
74
|
|
@@ -109,7 +110,7 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
109
110
|
it 'calls the explain debug method if enabled' do
|
110
111
|
suppress_output do
|
111
112
|
# Actually make it run all the way...but suppressing the output
|
112
|
-
subject.generate
|
113
|
+
subject.generate
|
113
114
|
end
|
114
115
|
end
|
115
116
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../support/spec_resources_active_model.rb'
|
4
|
+
require 'praxis/extensions/pagination'
|
5
|
+
|
6
|
+
class Book < Praxis::MediaType
|
7
|
+
attributes do
|
8
|
+
attribute :id, Integer
|
9
|
+
attribute :simple_name, String
|
10
|
+
attribute :category_uuid, String
|
11
|
+
end
|
12
|
+
end
|
13
|
+
Book.finalize!
|
14
|
+
BookPaginationParamsAttribute = Attributor::Attribute.new(Praxis::Types::PaginationParams.for(Book)) do
|
15
|
+
max_items 3
|
16
|
+
page_size 2
|
17
|
+
# disallow :paging
|
18
|
+
default by: :id
|
19
|
+
end
|
20
|
+
|
21
|
+
BookOrderingParamsAttribute = Attributor::Attribute.new(Praxis::Types::OrderingParams.for(Book)) do
|
22
|
+
enforce_for :all
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Praxis::Extensions::Pagination::ActiveRecordPaginationHandler do
|
26
|
+
shared_examples 'sorts_the_same' do |op, expected|
|
27
|
+
let(:order_params) { BookOrderingParamsAttribute.load(op) }
|
28
|
+
it do
|
29
|
+
loaded_ids = subject.all.map(&:id)
|
30
|
+
expected_ids = expected.all.map(&:id)
|
31
|
+
expect(loaded_ids).to eq(expected_ids)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
shared_examples 'paginates_the_same' do |par, expected|
|
36
|
+
let(:paginator_params) { BookPaginationParamsAttribute.load(par) }
|
37
|
+
it do
|
38
|
+
loaded_ids = subject.all.map(&:id)
|
39
|
+
expected_ids = expected.all.map(&:id)
|
40
|
+
expect(loaded_ids).to eq(expected_ids)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:query) { ActiveBook }
|
45
|
+
let(:table) { ActiveBook.table_name }
|
46
|
+
let(:paginator_params) { nil }
|
47
|
+
let(:order_params) { nil }
|
48
|
+
let(:pagination) do
|
49
|
+
Praxis::Extensions::Pagination::PaginationStruct.new(paginator_params, order_params)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
context '.paginate' do
|
54
|
+
subject {described_class.paginate(query, pagination) }
|
55
|
+
|
56
|
+
context 'empty struct' do
|
57
|
+
let(:paginator_params) { nil }
|
58
|
+
|
59
|
+
it 'does not change the query with an empty struct' do
|
60
|
+
expect(subject).to be(query)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'page-based' do
|
65
|
+
it_behaves_like 'paginates_the_same', 'page=1,items=3',
|
66
|
+
::ActiveBook.limit(3)
|
67
|
+
it_behaves_like 'paginates_the_same', 'page=2,items=3',
|
68
|
+
::ActiveBook.offset(3).limit(3)
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'page-based with defaults' do
|
72
|
+
it_behaves_like 'paginates_the_same', '',
|
73
|
+
::ActiveBook.offset(0).limit(2)
|
74
|
+
it_behaves_like 'paginates_the_same', 'page=2',
|
75
|
+
::ActiveBook.offset(2).limit(2)
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'cursor-based' do
|
79
|
+
it_behaves_like 'paginates_the_same', 'by=id,items=3',
|
80
|
+
::ActiveBook.limit(3).order(id: :asc)
|
81
|
+
it_behaves_like 'paginates_the_same', 'by=id,from=1000,items=3',
|
82
|
+
::ActiveBook.where("id > 1000").limit(3).order(id: :asc)
|
83
|
+
it_behaves_like 'paginates_the_same', 'by=simple_name,from=Book1000,items=3',
|
84
|
+
::ActiveBook.where("simple_name > 'Book1000'").limit(3).order(simple_name: :asc)
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'cursor-based with defaults' do
|
88
|
+
it_behaves_like 'paginates_the_same', '',
|
89
|
+
::ActiveBook.limit(2).order(id: :asc)
|
90
|
+
it_behaves_like 'paginates_the_same', 'by=id,from=1000',
|
91
|
+
::ActiveBook.where("id > 1000").limit(2).order(id: :asc)
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'including order' do
|
95
|
+
let(:order_params) { BookOrderingParamsAttribute.load(op_string) }
|
96
|
+
|
97
|
+
context 'when compatible with cursor' do
|
98
|
+
let(:op_string){ 'id'}
|
99
|
+
# Compatible cursor field
|
100
|
+
it_behaves_like 'paginates_the_same', 'by=id,items=3',
|
101
|
+
::ActiveBook.limit(3).order(id: :asc)
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when incompatible with cursor' do
|
105
|
+
let(:op_string){ 'id'}
|
106
|
+
let(:paginator_params) { BookPaginationParamsAttribute.load('by=simple_name,items=3') }
|
107
|
+
it do
|
108
|
+
expect{subject.all}.to raise_error(described_class::PaginationException, /is incompatible with pagination/)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context '.order' do
|
115
|
+
subject {described_class.order(query, pagination.order) }
|
116
|
+
|
117
|
+
it 'does not change the query with an empty struct' do
|
118
|
+
expect(subject).to be(query)
|
119
|
+
end
|
120
|
+
|
121
|
+
it_behaves_like 'sorts_the_same', 'simple_name',
|
122
|
+
::ActiveBook.order(simple_name: :asc)
|
123
|
+
it_behaves_like 'sorts_the_same', '-simple_name',
|
124
|
+
::ActiveBook.order(simple_name: :desc)
|
125
|
+
it_behaves_like 'sorts_the_same', '-simple_name,id',
|
126
|
+
::ActiveBook.order(simple_name: :desc, id: :asc)
|
127
|
+
it_behaves_like 'sorts_the_same', '-simple_name,-id',
|
128
|
+
::ActiveBook.order(simple_name: :desc, id: :desc)
|
129
|
+
end
|
130
|
+
end
|
data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_active_model.rb
RENAMED
@@ -36,6 +36,7 @@ def create_tables
|
|
36
36
|
create_table :active_taggings do |table|
|
37
37
|
table.column :book_id, :integer
|
38
38
|
table.column :tag_id, :integer
|
39
|
+
table.column :label, :string, null: true
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
@@ -48,8 +49,12 @@ class ActiveBook < ActiveRecord::Base
|
|
48
49
|
|
49
50
|
belongs_to :category, class_name: 'ActiveCategory', foreign_key: :category_uuid, primary_key: :uuid
|
50
51
|
belongs_to :author, class_name: 'ActiveAuthor'
|
52
|
+
|
51
53
|
has_many :taggings, class_name: 'ActiveTagging', foreign_key: :book_id
|
54
|
+
has_many :primary_taggings, lambda { where(label: 'primary')}, class_name: 'ActiveTagging', foreign_key: :book_id
|
55
|
+
|
52
56
|
has_many :tags, class_name: 'ActiveTag', through: :taggings
|
57
|
+
has_many :primary_tags, class_name: 'ActiveTag', through: :primary_taggings, source: :tag
|
53
58
|
end
|
54
59
|
|
55
60
|
class ActiveAuthor < ActiveRecord::Base
|
@@ -64,6 +69,8 @@ end
|
|
64
69
|
|
65
70
|
class ActiveTag < ActiveRecord::Base
|
66
71
|
include Praxis::Mapper::ActiveModelCompat
|
72
|
+
has_many :taggings, class_name: 'ActiveTagging', foreign_key: :tag_id
|
73
|
+
has_many :books, class_name: 'ActiveBook', through: :taggings, source: :book
|
67
74
|
end
|
68
75
|
|
69
76
|
class ActiveTagging < ActiveRecord::Base
|
@@ -94,6 +101,26 @@ end
|
|
94
101
|
class ActiveBookResource < ActiveBaseResource
|
95
102
|
model ActiveBook
|
96
103
|
|
104
|
+
filters_mapping(
|
105
|
+
id: :id,
|
106
|
+
category_uuid: :category_uuid,
|
107
|
+
'fake_nested.name': 'simple_name',
|
108
|
+
'name': 'simple_name',
|
109
|
+
'name_is_not': lambda do |spec| # Silly way to use a proc, but good enough for testing
|
110
|
+
{ name: :simple_name, value: spec[:value] , op: '!=' } # Can be an array for multiple conditions as well
|
111
|
+
end,
|
112
|
+
'author.id': 'author.id',
|
113
|
+
'author.name': 'author.name',
|
114
|
+
'taggings.label': 'taggings.label',
|
115
|
+
'taggings.tag_id': 'taggings.tag_id',
|
116
|
+
'taggings.tag.taggings.tag_id': 'taggings.tag.taggings.tag_id',
|
117
|
+
'tags.name': 'tags.name',
|
118
|
+
'primary_tags.name': 'primary_tags.name',
|
119
|
+
'category.name': 'category.name',
|
120
|
+
'category.books.name': 'category.books.simple_name',
|
121
|
+
'category.books.taggings.tag_id': 'category.books.taggings.tag_id',
|
122
|
+
'category.books.taggings.label': 'category.books.taggings.label',
|
123
|
+
)
|
97
124
|
# Forces to add an extra column (added_column)...and yet another (author_id) that will serve
|
98
125
|
# to check that if that's already automatically added due to an association, it won't interfere or duplicate
|
99
126
|
property :author, dependencies: [:author, :added_column, :author_id]
|
@@ -108,23 +135,39 @@ def seed_data
|
|
108
135
|
|
109
136
|
author1 = ActiveAuthor.create( id: 11, name: 'author1' )
|
110
137
|
author2 = ActiveAuthor.create( id: 22, name: 'author2' )
|
138
|
+
author3 = ActiveAuthor.create( id: 33, name: nil )
|
111
139
|
|
112
140
|
tag_blue = ActiveTag.create(id: 1 , name: 'blue' )
|
113
141
|
tag_red = ActiveTag.create(id: 2 , name: 'red' )
|
142
|
+
tag_green = ActiveTag.create(id: 3 , name: 'green' )
|
114
143
|
|
115
144
|
book1 = ActiveBook.create( id: 1 , simple_name: 'Book1', category_uuid: 'deadbeef1')
|
116
145
|
book1.author = author1
|
117
146
|
book1.category = cat1
|
118
147
|
book1.save
|
119
|
-
ActiveTagging.create(book: book1, tag: tag_blue)
|
148
|
+
ActiveTagging.create(book: book1, tag: tag_blue, label: 'primary')
|
120
149
|
ActiveTagging.create(book: book1, tag: tag_red)
|
150
|
+
ActiveTagging.create(book: book1, tag: tag_green, label: 'primary')
|
121
151
|
|
122
152
|
|
123
153
|
book2 = ActiveBook.create( id: 2 , simple_name: 'Book2', category_uuid: 'deadbeef1')
|
124
154
|
book2.author = author2
|
125
155
|
book2.category = cat2
|
126
156
|
book2.save
|
127
|
-
ActiveTagging.create(book: book2, tag: tag_red)
|
157
|
+
ActiveTagging.create(book: book2, tag: tag_red, label: 'primary')
|
158
|
+
|
159
|
+
book3 = ActiveBook.create( id: 3 , simple_name: 'Book3', category_uuid: 'deadbeef1')
|
160
|
+
book3.author = author3
|
161
|
+
book3.save
|
162
|
+
ActiveTagging.create(book: book3, tag: tag_red, label: 'primary')
|
163
|
+
|
164
|
+
# More stuff
|
165
|
+
|
166
|
+
10.times do |i|
|
167
|
+
bid = 1000+i
|
168
|
+
ActiveBook.create( id: bid , simple_name: "Book#{bid}")
|
169
|
+
end
|
170
|
+
|
128
171
|
end
|
129
172
|
|
130
173
|
seed_data
|