jsonapi-resources 0.9.12 → 0.10.0.beta1
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/LICENSE.txt +1 -1
- data/README.md +34 -11
- data/lib/bug_report_templates/rails_5_latest.rb +125 -0
- data/lib/bug_report_templates/rails_5_master.rb +140 -0
- data/lib/jsonapi-resources.rb +8 -3
- data/lib/jsonapi/active_relation_resource_finder.rb +640 -0
- data/lib/jsonapi/active_relation_resource_finder/join_tree.rb +126 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +121 -106
- data/lib/jsonapi/{cached_resource_fragment.rb → cached_response_fragment.rb} +13 -30
- data/lib/jsonapi/compiled_json.rb +11 -1
- data/lib/jsonapi/configuration.rb +44 -18
- data/lib/jsonapi/error.rb +27 -0
- data/lib/jsonapi/exceptions.rb +43 -40
- data/lib/jsonapi/formatter.rb +3 -3
- data/lib/jsonapi/include_directives.rb +2 -45
- data/lib/jsonapi/link_builder.rb +87 -80
- data/lib/jsonapi/operation.rb +16 -5
- data/lib/jsonapi/operation_result.rb +74 -16
- data/lib/jsonapi/processor.rb +233 -112
- data/lib/jsonapi/relationship.rb +77 -53
- data/lib/jsonapi/request_parser.rb +378 -423
- data/lib/jsonapi/resource.rb +224 -524
- data/lib/jsonapi/resource_controller_metal.rb +2 -2
- data/lib/jsonapi/resource_fragment.rb +47 -0
- data/lib/jsonapi/resource_id_tree.rb +112 -0
- data/lib/jsonapi/resource_identity.rb +42 -0
- data/lib/jsonapi/resource_serializer.rb +133 -301
- data/lib/jsonapi/resource_set.rb +108 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +100 -88
- data/lib/jsonapi/routing_ext.rb +21 -43
- metadata +29 -45
- data/lib/jsonapi/operation_dispatcher.rb +0 -88
- data/lib/jsonapi/operation_results.rb +0 -35
- data/lib/jsonapi/relationship_builder.rb +0 -167
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1da47a9966931af4ff9a5bab22e41097bf9075bf
|
4
|
+
data.tar.gz: 7ed9248ebb089d4c3f76b03d9e1eb47c8810be75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f8a2a940460ca620cf2495856523c2cab00e72ee169ecc9c7bb84cb3499d96e4fe9a39b39f439b116bef24417e766cd93f8b2bf2be98e397cfe4994806abcc8
|
7
|
+
data.tar.gz: c54161c677a2b7dcf1db569bf1c05685ce8d5b84349157f7ab6f3b927c0a357322b57f762b2feeb8225ca8f9e04aeb63aa8313467d77e1f0e1d7d44616ff3357
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# JSONAPI::Resources [](https://badge.fury.io/rb/jsonapi-resources) [](https://badge.fury.io/rb/jsonapi-resources) [](http://travis-ci.org/cerebris/jsonapi-resources) [](https://codeclimate.com/github/cerebris/jsonapi-resources)
|
2
2
|
|
3
3
|
[](https://gitter.im/cerebris/jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
4
4
|
|
@@ -13,7 +13,7 @@ backed by ActiveRecord models or by custom objects.
|
|
13
13
|
|
14
14
|
## Documentation
|
15
15
|
|
16
|
-
Full documentation can be found at [http://jsonapi-resources.com](http://jsonapi-resources.com), including the [v0.
|
16
|
+
Full documentation can be found at [http://jsonapi-resources.com](http://jsonapi-resources.com), including the [v0.10 alpha Guide](http://jsonapi-resources.com/v0.10/guide/) specific to this version.
|
17
17
|
|
18
18
|
## Demo App
|
19
19
|
|
@@ -28,26 +28,49 @@ which *should* be compatible with JSON:API compliant server implementations such
|
|
28
28
|
|
29
29
|
Add JR to your application's `Gemfile`:
|
30
30
|
|
31
|
-
|
31
|
+
```
|
32
|
+
gem 'jsonapi-resources'
|
33
|
+
```
|
32
34
|
|
33
35
|
And then execute:
|
34
36
|
|
35
|
-
|
37
|
+
```bash
|
38
|
+
bundle
|
39
|
+
```
|
36
40
|
|
37
41
|
Or install it yourself as:
|
38
42
|
|
39
|
-
|
43
|
+
```bash
|
44
|
+
gem install jsonapi-resources
|
45
|
+
```
|
40
46
|
|
41
|
-
**For further usage see the [v0.
|
47
|
+
**For further usage see the [v0.10 alpha Guide](http://jsonapi-resources.com/v0.10/guide/)**
|
42
48
|
|
43
49
|
## Contributing
|
44
50
|
|
51
|
+
1. Submit an issue describing any new features you wish it add or the bug you intend to fix
|
45
52
|
1. Fork it ( http://github.com/cerebris/jsonapi-resources/fork )
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
53
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
1. Run the full test suite (`rake test`)
|
55
|
+
1. Fix any failing tests
|
56
|
+
1. Commit your changes (`git commit -am 'Add some feature'`)
|
57
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
58
|
+
1. Create a new Pull Request
|
59
|
+
|
60
|
+
## Did you find a bug?
|
61
|
+
|
62
|
+
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/cerebris/jsonapi-resources/issues).
|
63
|
+
|
64
|
+
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/cerebris/jsonapi-resources/issues/new).
|
65
|
+
Be sure to include a **title and clear description**, as much relevant information as possible,
|
66
|
+
and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
|
67
|
+
|
68
|
+
* If possible, use the relevant bug report templates to create the issue.
|
69
|
+
Simply copy the content of the appropriate template into a .rb file, make the necessary changes to demonstrate the issue,
|
70
|
+
and **paste the content into the issue description or attach as a file**:
|
71
|
+
* [**Rails 5** issues](https://github.com/cerebris/jsonapi-resources/blob/master/lib/bug_report_templates/rails_5_master.rb)
|
72
|
+
|
50
73
|
|
51
74
|
## License
|
52
75
|
|
53
|
-
Copyright 2014-
|
76
|
+
Copyright 2014-2017 Cerebris Corporation. MIT License (see LICENSE for details).
|
@@ -0,0 +1,125 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/inline'
|
3
|
+
rescue LoadError => e
|
4
|
+
STDERR.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
|
5
|
+
raise e
|
6
|
+
end
|
7
|
+
|
8
|
+
gemfile(true) do
|
9
|
+
source 'https://rubygems.org'
|
10
|
+
|
11
|
+
gem 'rails', require: false
|
12
|
+
gem 'sqlite3', platform: :mri
|
13
|
+
|
14
|
+
gem 'activerecord-jdbcsqlite3-adapter',
|
15
|
+
git: 'https://github.com/jruby/activerecord-jdbc-adapter',
|
16
|
+
branch: 'rails-5',
|
17
|
+
platform: :jruby
|
18
|
+
|
19
|
+
gem 'jsonapi-resources', require: false
|
20
|
+
end
|
21
|
+
|
22
|
+
# prepare active_record database
|
23
|
+
require 'active_record'
|
24
|
+
|
25
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
26
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
27
|
+
|
28
|
+
ActiveRecord::Schema.define do
|
29
|
+
# Add your schema here
|
30
|
+
create_table :your_models, force: true do |t|
|
31
|
+
t.string :name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# create models
|
36
|
+
class YourModel < ActiveRecord::Base
|
37
|
+
end
|
38
|
+
|
39
|
+
# prepare rails app
|
40
|
+
require 'action_controller/railtie'
|
41
|
+
# require 'action_view/railtie'
|
42
|
+
require 'jsonapi-resources'
|
43
|
+
|
44
|
+
class ApplicationController < ActionController::Base
|
45
|
+
end
|
46
|
+
|
47
|
+
# prepare jsonapi resources and controllers
|
48
|
+
class YourModelsController < ApplicationController
|
49
|
+
include JSONAPI::ActsAsResourceController
|
50
|
+
end
|
51
|
+
|
52
|
+
class YourModelResource < JSONAPI::Resource
|
53
|
+
attribute :name
|
54
|
+
filter :name
|
55
|
+
end
|
56
|
+
|
57
|
+
class TestApp < Rails::Application
|
58
|
+
config.root = File.dirname(__FILE__)
|
59
|
+
config.logger = Logger.new(STDOUT)
|
60
|
+
Rails.logger = config.logger
|
61
|
+
|
62
|
+
secrets.secret_token = 'secret_token'
|
63
|
+
secrets.secret_key_base = 'secret_key_base'
|
64
|
+
|
65
|
+
config.eager_load = false
|
66
|
+
end
|
67
|
+
|
68
|
+
# initialize app
|
69
|
+
Rails.application.initialize!
|
70
|
+
|
71
|
+
JSONAPI.configure do |config|
|
72
|
+
config.json_key_format = :underscored_key
|
73
|
+
config.route_format = :underscored_key
|
74
|
+
end
|
75
|
+
|
76
|
+
# draw routes
|
77
|
+
Rails.application.routes.draw do
|
78
|
+
jsonapi_resources :your_models, only: [:index, :create]
|
79
|
+
end
|
80
|
+
|
81
|
+
# prepare tests
|
82
|
+
require 'minitest/autorun'
|
83
|
+
require 'rack/test'
|
84
|
+
|
85
|
+
# Replace this with the code necessary to make your test fail.
|
86
|
+
class BugTest < Minitest::Test
|
87
|
+
include Rack::Test::Methods
|
88
|
+
|
89
|
+
def json_api_headers
|
90
|
+
{'Accept' => JSONAPI::MEDIA_TYPE, 'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE}
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_index_your_models
|
94
|
+
record = YourModel.create! name: 'John Doe'
|
95
|
+
get '/your_models', nil, json_api_headers
|
96
|
+
assert last_response.ok?
|
97
|
+
json_response = JSON.parse(last_response.body)
|
98
|
+
refute_nil json_response['data']
|
99
|
+
refute_empty json_response['data']
|
100
|
+
refute_empty json_response['data'].first
|
101
|
+
assert record.id.to_s, json_response['data'].first['id']
|
102
|
+
assert 'your_models', json_response['data'].first['type']
|
103
|
+
assert({'name' => 'John Doe'}, json_response['data'].first['attributes'])
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_create_your_models
|
107
|
+
json_request = {
|
108
|
+
'data' => {
|
109
|
+
type: 'your_models',
|
110
|
+
attributes: {
|
111
|
+
name: 'Jane Doe'
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
post '/your_models', json_request.to_json, json_api_headers
|
116
|
+
assert last_response.created?
|
117
|
+
refute_nil YourModel.find_by(name: 'Jane Doe')
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def app
|
123
|
+
Rails.application
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/inline'
|
3
|
+
require 'bundler'
|
4
|
+
rescue LoadError => e
|
5
|
+
STDERR.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
|
6
|
+
raise e
|
7
|
+
end
|
8
|
+
|
9
|
+
gemfile(true, ui: ENV['SILENT'] ? Bundler::UI::Silent.new : Bundler::UI::Shell.new) do
|
10
|
+
source 'https://rubygems.org'
|
11
|
+
|
12
|
+
gem 'rails', require: false
|
13
|
+
gem 'sqlite3', platform: :mri
|
14
|
+
|
15
|
+
gem 'activerecord-jdbcsqlite3-adapter',
|
16
|
+
git: 'https://github.com/jruby/activerecord-jdbc-adapter',
|
17
|
+
branch: 'rails-5',
|
18
|
+
platform: :jruby
|
19
|
+
|
20
|
+
if ENV['JSONAPI_RESOURCES_PATH']
|
21
|
+
gem 'jsonapi-resources', path: ENV['JSONAPI_RESOURCES_PATH'], require: false
|
22
|
+
else
|
23
|
+
gem 'jsonapi-resources', git: 'https://github.com/cerebris/jsonapi-resources', require: false
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# prepare active_record database
|
29
|
+
require 'active_record'
|
30
|
+
|
31
|
+
class NullLogger < Logger
|
32
|
+
def initialize(*_args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add(*_args, &_block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
40
|
+
ActiveRecord::Base.logger = ENV['SILENT'] ? NullLogger.new : Logger.new(STDOUT)
|
41
|
+
ActiveRecord::Migration.verbose = !ENV['SILENT']
|
42
|
+
|
43
|
+
ActiveRecord::Schema.define do
|
44
|
+
# Add your schema here
|
45
|
+
create_table :your_models, force: true do |t|
|
46
|
+
t.string :name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# create models
|
51
|
+
class YourModel < ActiveRecord::Base
|
52
|
+
end
|
53
|
+
|
54
|
+
# prepare rails app
|
55
|
+
require 'action_controller/railtie'
|
56
|
+
# require 'action_view/railtie'
|
57
|
+
require 'jsonapi-resources'
|
58
|
+
|
59
|
+
class ApplicationController < ActionController::Base
|
60
|
+
end
|
61
|
+
|
62
|
+
# prepare jsonapi resources and controllers
|
63
|
+
class YourModelsController < ApplicationController
|
64
|
+
include JSONAPI::ActsAsResourceController
|
65
|
+
end
|
66
|
+
|
67
|
+
class YourModelResource < JSONAPI::Resource
|
68
|
+
attribute :name
|
69
|
+
filter :name
|
70
|
+
end
|
71
|
+
|
72
|
+
class TestApp < Rails::Application
|
73
|
+
config.root = File.dirname(__FILE__)
|
74
|
+
config.logger = ENV['SILENT'] ? NullLogger.new : Logger.new(STDOUT)
|
75
|
+
Rails.logger = config.logger
|
76
|
+
|
77
|
+
secrets.secret_token = 'secret_token'
|
78
|
+
secrets.secret_key_base = 'secret_key_base'
|
79
|
+
|
80
|
+
config.eager_load = false
|
81
|
+
end
|
82
|
+
|
83
|
+
# initialize app
|
84
|
+
Rails.application.initialize!
|
85
|
+
|
86
|
+
JSONAPI.configure do |config|
|
87
|
+
config.json_key_format = :underscored_key
|
88
|
+
config.route_format = :underscored_key
|
89
|
+
end
|
90
|
+
|
91
|
+
# draw routes
|
92
|
+
Rails.application.routes.draw do
|
93
|
+
jsonapi_resources :your_models, only: [:index, :create]
|
94
|
+
end
|
95
|
+
|
96
|
+
# prepare tests
|
97
|
+
require 'minitest/autorun'
|
98
|
+
require 'rack/test'
|
99
|
+
|
100
|
+
# Replace this with the code necessary to make your test fail.
|
101
|
+
class BugTest < Minitest::Test
|
102
|
+
include Rack::Test::Methods
|
103
|
+
|
104
|
+
def json_api_headers
|
105
|
+
{'Accept' => JSONAPI::MEDIA_TYPE, 'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE}
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_index_your_models
|
109
|
+
record = YourModel.create! name: 'John Doe'
|
110
|
+
get '/your_models', nil, json_api_headers
|
111
|
+
assert last_response.ok?
|
112
|
+
json_response = JSON.parse(last_response.body)
|
113
|
+
refute_nil json_response['data']
|
114
|
+
refute_empty json_response['data']
|
115
|
+
refute_empty json_response['data'].first
|
116
|
+
assert record.id.to_s, json_response['data'].first['id']
|
117
|
+
assert 'your_models', json_response['data'].first['type']
|
118
|
+
assert({'name' => 'John Doe'}, json_response['data'].first['attributes'])
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_create_your_models
|
122
|
+
json_request = {
|
123
|
+
'data' => {
|
124
|
+
type: 'your_models',
|
125
|
+
attributes: {
|
126
|
+
name: 'Jane Doe'
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
post '/your_models', json_request.to_json, json_api_headers
|
131
|
+
assert last_response.created?
|
132
|
+
refute_nil YourModel.find_by(name: 'Jane Doe')
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def app
|
138
|
+
Rails.application
|
139
|
+
end
|
140
|
+
end
|
data/lib/jsonapi-resources.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'jsonapi/naive_cache'
|
2
2
|
require 'jsonapi/compiled_json'
|
3
3
|
require 'jsonapi/resource'
|
4
|
-
require 'jsonapi/
|
4
|
+
require 'jsonapi/cached_response_fragment'
|
5
5
|
require 'jsonapi/response_document'
|
6
6
|
require 'jsonapi/acts_as_resource_controller'
|
7
7
|
require 'jsonapi/resource_controller'
|
@@ -17,11 +17,16 @@ require 'jsonapi/exceptions'
|
|
17
17
|
require 'jsonapi/error'
|
18
18
|
require 'jsonapi/error_codes'
|
19
19
|
require 'jsonapi/request_parser'
|
20
|
-
require 'jsonapi/operation_dispatcher'
|
21
20
|
require 'jsonapi/processor'
|
22
21
|
require 'jsonapi/relationship'
|
23
22
|
require 'jsonapi/include_directives'
|
23
|
+
require 'jsonapi/operation'
|
24
24
|
require 'jsonapi/operation_result'
|
25
|
-
require 'jsonapi/operation_results'
|
26
25
|
require 'jsonapi/callbacks'
|
27
26
|
require 'jsonapi/link_builder'
|
27
|
+
require 'jsonapi/active_relation_resource_finder'
|
28
|
+
require 'jsonapi/active_relation_resource_finder/join_tree'
|
29
|
+
require 'jsonapi/resource_identity'
|
30
|
+
require 'jsonapi/resource_fragment'
|
31
|
+
require 'jsonapi/resource_id_tree'
|
32
|
+
require 'jsonapi/resource_set'
|
@@ -0,0 +1,640 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module ActiveRelationResourceFinder
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Finds Resources using the `filters`. Pagination and sort options are used when provided
|
10
|
+
#
|
11
|
+
# @param filters [Hash] the filters hash
|
12
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
13
|
+
# @option options [Hash] :sort_criteria The `sort criteria`
|
14
|
+
# @option options [Hash] :include_directives The `include_directives`
|
15
|
+
#
|
16
|
+
# @return [Array<Resource>] the Resource instances matching the filters, sorting and pagination rules.
|
17
|
+
def find(filters, options = {})
|
18
|
+
records = find_records(filters, options)
|
19
|
+
resources_for(records, options[:context])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Counts Resources found using the `filters`
|
23
|
+
#
|
24
|
+
# @param filters [Hash] the filters hash
|
25
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
26
|
+
#
|
27
|
+
# @return [Integer] the count
|
28
|
+
def count(filters, options = {})
|
29
|
+
count_records(filter_records(records(options), filters, options))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the single Resource identified by `key`
|
33
|
+
#
|
34
|
+
# @param key the primary key of the resource to find
|
35
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
36
|
+
def find_by_key(key, options = {})
|
37
|
+
record = find_record_by_key(key, options)
|
38
|
+
resource_for(record, options[:context])
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an array of Resources identified by the `keys` array
|
42
|
+
#
|
43
|
+
# @param keys [Array<key>] Array of primary keys to find resources for
|
44
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
45
|
+
def find_by_keys(keys, options = {})
|
46
|
+
records = find_records_by_keys(keys, options)
|
47
|
+
resources_for(records, options[:context])
|
48
|
+
end
|
49
|
+
|
50
|
+
# Finds Resource fragments using the `filters`. Pagination and sort options are used when provided.
|
51
|
+
# Retrieving the ResourceIdentities and attributes does not instantiate a model instance.
|
52
|
+
#
|
53
|
+
# @param filters [Hash] the filters hash
|
54
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
55
|
+
# @option options [Hash] :sort_criteria The `sort criteria`
|
56
|
+
# @option options [Hash] :include_directives The `include_directives`
|
57
|
+
# @option options [Hash] :attributes Additional fields to be retrieved.
|
58
|
+
# @option options [Boolean] :cache Return the resources' cache field
|
59
|
+
#
|
60
|
+
# @return [Hash{ResourceIdentity => {identity: => ResourceIdentity, cache: cache_field, attributes: => {name => value}}}]
|
61
|
+
# the ResourceInstances matching the filters, sorting, and pagination rules along with any request
|
62
|
+
# additional_field values
|
63
|
+
def find_fragments(filters, options = {})
|
64
|
+
records = find_records(filters, options)
|
65
|
+
|
66
|
+
table_name = _model_class.table_name
|
67
|
+
pluck_fields = [Arel.sql("#{concat_table_field(table_name, _primary_key)} AS #{table_name}_#{_primary_key}")]
|
68
|
+
|
69
|
+
cache_field = attribute_to_model_field(:_cache_field) if options[:cache]
|
70
|
+
if cache_field
|
71
|
+
pluck_fields << Arel.sql("#{concat_table_field(table_name, cache_field[:name])} AS #{table_name}_#{cache_field[:name]}")
|
72
|
+
end
|
73
|
+
|
74
|
+
model_fields = {}
|
75
|
+
attributes = options[:attributes]
|
76
|
+
attributes.try(:each) do |attribute|
|
77
|
+
model_field = attribute_to_model_field(attribute)
|
78
|
+
model_fields[attribute] = model_field
|
79
|
+
pluck_fields << Arel.sql("#{concat_table_field(table_name, model_field[:name])} AS #{table_name}_#{model_field[:name]}")
|
80
|
+
end
|
81
|
+
|
82
|
+
fragments = {}
|
83
|
+
records.pluck(*pluck_fields).collect do |row|
|
84
|
+
rid = JSONAPI::ResourceIdentity.new(self, pluck_fields.length == 1 ? row : row[0])
|
85
|
+
|
86
|
+
fragments[rid] ||= JSONAPI::ResourceFragment.new(rid)
|
87
|
+
attributes_offset = 1
|
88
|
+
|
89
|
+
if cache_field
|
90
|
+
fragments[rid].cache = cast_to_attribute_type(row[1], cache_field[:type])
|
91
|
+
attributes_offset+= 1
|
92
|
+
end
|
93
|
+
|
94
|
+
model_fields.each_with_index do |k, idx|
|
95
|
+
fragments[rid].attributes[k[0]]= cast_to_attribute_type(row[idx + attributes_offset], k[1][:type])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
fragments
|
100
|
+
end
|
101
|
+
|
102
|
+
# Finds Resource Fragments related to the source resources through the specified relationship
|
103
|
+
#
|
104
|
+
# @param source_rids [Array<ResourceIdentity>] The resources to find related ResourcesIdentities for
|
105
|
+
# @param relationship_name [String | Symbol] The name of the relationship
|
106
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
107
|
+
# @option options [Hash] :attributes Additional fields to be retrieved.
|
108
|
+
# @option options [Boolean] :cache Return the resources' cache field
|
109
|
+
#
|
110
|
+
# @return [Hash{ResourceIdentity => {identity: => ResourceIdentity, cache: cache_field, attributes: => {name => value}, related: {relationship_name: [] }}}]
|
111
|
+
# the ResourceInstances matching the filters, sorting, and pagination rules along with any request
|
112
|
+
# additional_field values
|
113
|
+
def find_related_fragments(source_rids, relationship_name, options = {})
|
114
|
+
relationship = _relationship(relationship_name)
|
115
|
+
|
116
|
+
if relationship.polymorphic? && relationship.foreign_key_on == :self
|
117
|
+
find_related_polymorphic_fragments(source_rids, relationship, options, false)
|
118
|
+
else
|
119
|
+
find_related_monomorphic_fragments(source_rids, relationship, options, false)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def find_included_fragments(source_rids, relationship_name, options = {})
|
124
|
+
relationship = _relationship(relationship_name)
|
125
|
+
|
126
|
+
if relationship.polymorphic? && relationship.foreign_key_on == :self
|
127
|
+
find_related_polymorphic_fragments(source_rids, relationship, options, true)
|
128
|
+
else
|
129
|
+
find_related_monomorphic_fragments(source_rids, relationship, options, true)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Counts Resources related to the source resource through the specified relationship
|
134
|
+
#
|
135
|
+
# @param source_rid [ResourceIdentity] Source resource identifier
|
136
|
+
# @param relationship_name [String | Symbol] The name of the relationship
|
137
|
+
# @option options [Hash] :context The context of the request, set in the controller
|
138
|
+
#
|
139
|
+
# @return [Integer] the count
|
140
|
+
def count_related(source_rid, relationship_name, options = {})
|
141
|
+
opts = options.dup
|
142
|
+
|
143
|
+
relationship = _relationship(relationship_name)
|
144
|
+
related_klass = relationship.resource_klass
|
145
|
+
|
146
|
+
context = opts[:context]
|
147
|
+
|
148
|
+
primary_key_field = "#{_table_name}.#{_primary_key}"
|
149
|
+
|
150
|
+
records = records(context: context).where(primary_key_field => source_rid.id)
|
151
|
+
|
152
|
+
# join in related to the source records
|
153
|
+
records, related_alias = get_join_alias(records) { |records| records.joins(relationship.relation_name(opts)) }
|
154
|
+
|
155
|
+
join_tree = JoinTree.new(resource_klass: related_klass,
|
156
|
+
source_relationship: relationship,
|
157
|
+
filters: filters,
|
158
|
+
options: opts)
|
159
|
+
|
160
|
+
records, joins = apply_joins(records, join_tree, opts)
|
161
|
+
|
162
|
+
# Options for filtering
|
163
|
+
opts[:joins] = joins
|
164
|
+
opts[:related_alias] = related_alias
|
165
|
+
|
166
|
+
filters = opts.fetch(:filters, {})
|
167
|
+
records = related_klass.filter_records(records, filters, opts)
|
168
|
+
|
169
|
+
records.count(:all)
|
170
|
+
end
|
171
|
+
|
172
|
+
def parse_relationship_path(path)
|
173
|
+
relationships = []
|
174
|
+
relationship_names = []
|
175
|
+
field = nil
|
176
|
+
|
177
|
+
current_path = path
|
178
|
+
current_resource_klass = self
|
179
|
+
loop do
|
180
|
+
parts = current_path.to_s.partition('.')
|
181
|
+
relationship = current_resource_klass._relationship(parts[0])
|
182
|
+
if relationship
|
183
|
+
relationships << relationship
|
184
|
+
relationship_names << relationship.name
|
185
|
+
else
|
186
|
+
if parts[2].blank?
|
187
|
+
field = parts[0]
|
188
|
+
break
|
189
|
+
else
|
190
|
+
# :nocov:
|
191
|
+
warn "Unknown relationship #{parts[0]}"
|
192
|
+
# :nocov:
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
current_resource_klass = relationship.resource_klass
|
197
|
+
|
198
|
+
if parts[2].include?('.')
|
199
|
+
current_path = parts[2]
|
200
|
+
else
|
201
|
+
relationship = current_resource_klass._relationship(parts[2])
|
202
|
+
if relationship
|
203
|
+
relationships << relationship
|
204
|
+
relationship_names << relationship.name
|
205
|
+
else
|
206
|
+
field = parts[2]
|
207
|
+
end
|
208
|
+
break
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
return relationships, relationship_names.join('.'), field
|
213
|
+
end
|
214
|
+
|
215
|
+
protected
|
216
|
+
|
217
|
+
def find_record_by_key(key, options = {})
|
218
|
+
records = find_records({ _primary_key => key }, options.except(:paginator, :sort_criteria))
|
219
|
+
record = records.first
|
220
|
+
fail JSONAPI::Exceptions::RecordNotFound.new(key) if record.nil?
|
221
|
+
record
|
222
|
+
end
|
223
|
+
|
224
|
+
def find_records_by_keys(keys, options = {})
|
225
|
+
records(options).where({ _primary_key => keys })
|
226
|
+
end
|
227
|
+
|
228
|
+
def find_related_monomorphic_fragments(source_rids, relationship, options, connect_source_identity)
|
229
|
+
opts = options.dup
|
230
|
+
|
231
|
+
source_ids = source_rids.collect {|rid| rid.id}
|
232
|
+
|
233
|
+
context = opts[:context]
|
234
|
+
|
235
|
+
related_klass = relationship.resource_klass
|
236
|
+
|
237
|
+
primary_key_field = "#{_table_name}.#{_primary_key}"
|
238
|
+
|
239
|
+
records = records(context: context).where(primary_key_field => source_ids)
|
240
|
+
|
241
|
+
# join in related to the source records
|
242
|
+
records, related_alias = get_join_alias(records) { |records| records.joins(relationship.relation_name(opts)) }
|
243
|
+
|
244
|
+
sort_criteria = []
|
245
|
+
opts[:sort_criteria].try(:each) do |sort|
|
246
|
+
field = sort[:field].to_s == 'id' ? related_klass._primary_key : sort[:field]
|
247
|
+
sort_criteria << { field: field, direction: sort[:direction] }
|
248
|
+
end
|
249
|
+
|
250
|
+
paginator = opts[:paginator]
|
251
|
+
|
252
|
+
filters = opts.fetch(:filters, {})
|
253
|
+
|
254
|
+
# Joins in this case are related to the related_klass
|
255
|
+
join_tree = JoinTree.new(resource_klass: related_klass,
|
256
|
+
source_relationship: relationship,
|
257
|
+
filters: filters,
|
258
|
+
sort_criteria: sort_criteria,
|
259
|
+
options: opts)
|
260
|
+
|
261
|
+
records, joins = apply_joins(records, join_tree, opts)
|
262
|
+
|
263
|
+
# Options for filtering
|
264
|
+
opts[:joins] = joins
|
265
|
+
opts[:related_alias] = related_alias
|
266
|
+
|
267
|
+
records = related_klass.filter_records(records, filters, opts)
|
268
|
+
|
269
|
+
order_options = related_klass.construct_order_options(sort_criteria)
|
270
|
+
|
271
|
+
# ToDO: Remove count check. Currently pagination isn't working with multiple source_rids (i.e. it only works
|
272
|
+
# for show relationships, not related includes).
|
273
|
+
if paginator && source_rids.count == 1
|
274
|
+
records = related_klass.apply_pagination(records, paginator, order_options)
|
275
|
+
end
|
276
|
+
|
277
|
+
records = sort_records(records, order_options, opts)
|
278
|
+
|
279
|
+
pluck_fields = [
|
280
|
+
Arel.sql(primary_key_field),
|
281
|
+
Arel.sql("#{concat_table_field(related_alias, related_klass._primary_key)} AS #{related_alias}_#{related_klass._primary_key}")
|
282
|
+
]
|
283
|
+
|
284
|
+
cache_field = related_klass.attribute_to_model_field(:_cache_field) if opts[:cache]
|
285
|
+
if cache_field
|
286
|
+
pluck_fields << Arel.sql("#{concat_table_field(related_alias, cache_field[:name])} AS #{related_alias}_#{cache_field[:name]}")
|
287
|
+
end
|
288
|
+
|
289
|
+
model_fields = {}
|
290
|
+
attributes = opts[:attributes]
|
291
|
+
attributes.try(:each) do |attribute|
|
292
|
+
model_field = related_klass.attribute_to_model_field(attribute)
|
293
|
+
model_fields[attribute] = model_field
|
294
|
+
pluck_fields << Arel.sql("#{concat_table_field(related_alias, model_field[:name])} AS #{related_alias}_#{model_field[:name]}")
|
295
|
+
end
|
296
|
+
|
297
|
+
rows = records.pluck(*pluck_fields)
|
298
|
+
|
299
|
+
related_fragments = {}
|
300
|
+
|
301
|
+
rows.each do |row|
|
302
|
+
unless row[1].nil?
|
303
|
+
rid = JSONAPI::ResourceIdentity.new(related_klass, row[1])
|
304
|
+
|
305
|
+
related_fragments[rid] ||= JSONAPI::ResourceFragment.new(rid)
|
306
|
+
|
307
|
+
attributes_offset = 2
|
308
|
+
|
309
|
+
if cache_field
|
310
|
+
related_fragments[rid].cache = cast_to_attribute_type(row[attributes_offset], cache_field[:type])
|
311
|
+
attributes_offset+= 1
|
312
|
+
end
|
313
|
+
|
314
|
+
model_fields.each_with_index do |k, idx|
|
315
|
+
related_fragments[rid].attributes[k[0]] = cast_to_attribute_type(row[idx + attributes_offset], k[1][:type])
|
316
|
+
end
|
317
|
+
|
318
|
+
source_rid = JSONAPI::ResourceIdentity.new(self, row[0])
|
319
|
+
|
320
|
+
related_fragments[rid].add_related_from(source_rid)
|
321
|
+
|
322
|
+
if connect_source_identity
|
323
|
+
related_relationship = related_klass._relationships[relationship.inverse_relationship]
|
324
|
+
if related_relationship
|
325
|
+
related_fragments[rid].add_related_identity(related_relationship.name, source_rid)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
related_fragments
|
332
|
+
end
|
333
|
+
|
334
|
+
# Gets resource identities where the related resource is polymorphic and the resource type and id
|
335
|
+
# are stored on the primary resources. Cache fields will always be on the related resources.
|
336
|
+
def find_related_polymorphic_fragments(source_rids, relationship, options, connect_source_identity)
|
337
|
+
source_ids = source_rids.collect {|rid| rid.id}
|
338
|
+
|
339
|
+
context = options[:context]
|
340
|
+
|
341
|
+
records = records(context: context)
|
342
|
+
|
343
|
+
primary_key = concat_table_field(_table_name, _primary_key)
|
344
|
+
related_key = concat_table_field(_table_name, relationship.foreign_key)
|
345
|
+
related_type = concat_table_field(_table_name, relationship.polymorphic_type)
|
346
|
+
|
347
|
+
pluck_fields = [
|
348
|
+
Arel.sql("#{primary_key} AS #{_table_name}_#{_primary_key}"),
|
349
|
+
Arel.sql("#{related_key} AS #{_table_name}_#{relationship.foreign_key}"),
|
350
|
+
Arel.sql("#{related_type} AS #{_table_name}_#{relationship.polymorphic_type}")
|
351
|
+
]
|
352
|
+
|
353
|
+
relations = relationship.polymorphic_relations
|
354
|
+
|
355
|
+
# Get the additional fields from each relation. There's a limitation that the fields must exist in each relation
|
356
|
+
|
357
|
+
relation_positions = {}
|
358
|
+
relation_index = 3
|
359
|
+
|
360
|
+
attributes = options.fetch(:attributes, [])
|
361
|
+
|
362
|
+
if relations.nil? || relations.length == 0
|
363
|
+
# :nocov:
|
364
|
+
warn "No relations found for polymorphic relationship."
|
365
|
+
# :nocov:
|
366
|
+
else
|
367
|
+
relations.try(:each) do |relation|
|
368
|
+
related_klass = resource_klass_for(relation.to_s)
|
369
|
+
|
370
|
+
cache_field = related_klass.attribute_to_model_field(:_cache_field) if options[:cache]
|
371
|
+
|
372
|
+
# We only need to join the relations if we are getting additional fields
|
373
|
+
if cache_field || attributes.length > 0
|
374
|
+
records, table_alias = get_join_alias(records) { |records| records.left_joins(relation.to_sym) }
|
375
|
+
|
376
|
+
if cache_field
|
377
|
+
pluck_fields << concat_table_field(table_alias, cache_field[:name])
|
378
|
+
end
|
379
|
+
|
380
|
+
model_fields = {}
|
381
|
+
attributes.try(:each) do |attribute|
|
382
|
+
model_field = related_klass.attribute_to_model_field(attribute)
|
383
|
+
model_fields[attribute] = model_field
|
384
|
+
end
|
385
|
+
|
386
|
+
model_fields.each do |_k, v|
|
387
|
+
pluck_fields << concat_table_field(table_alias, v[:name])
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
related = related_klass._model_class.name
|
393
|
+
relation_positions[related] = { relation_klass: related_klass,
|
394
|
+
cache_field: cache_field,
|
395
|
+
model_fields: model_fields,
|
396
|
+
field_offset: relation_index}
|
397
|
+
|
398
|
+
relation_index+= 1 if cache_field
|
399
|
+
relation_index+= attributes.length if attributes.length > 0
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
primary_resource_filters = options[:filters]
|
404
|
+
primary_resource_filters ||= {}
|
405
|
+
|
406
|
+
primary_resource_filters[_primary_key] = source_ids
|
407
|
+
|
408
|
+
records = apply_filters(records, primary_resource_filters, options)
|
409
|
+
|
410
|
+
rows = records.pluck(*pluck_fields)
|
411
|
+
|
412
|
+
related_fragments = {}
|
413
|
+
|
414
|
+
rows.each do |row|
|
415
|
+
unless row[1].nil? || row[2].nil?
|
416
|
+
related_klass = resource_klass_for(row[2])
|
417
|
+
|
418
|
+
rid = JSONAPI::ResourceIdentity.new(related_klass, row[1])
|
419
|
+
related_fragments[rid] ||= JSONAPI::ResourceFragment.new(rid)
|
420
|
+
|
421
|
+
source_rid = JSONAPI::ResourceIdentity.new(self, row[0])
|
422
|
+
related_fragments[rid].add_related_from(source_rid)
|
423
|
+
|
424
|
+
if connect_source_identity
|
425
|
+
related_relationship = related_klass._relationships[relationship.inverse_relationship]
|
426
|
+
if related_relationship
|
427
|
+
related_fragments[rid].add_related_identity(related_relationship.name, source_rid)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
relation_position = relation_positions[row[2]]
|
432
|
+
model_fields = relation_position[:model_fields]
|
433
|
+
cache_field = relation_position[:cache_field]
|
434
|
+
field_offset = relation_position[:field_offset]
|
435
|
+
|
436
|
+
attributes_offset = 0
|
437
|
+
|
438
|
+
if cache_field
|
439
|
+
related_fragments[rid].cache = cast_to_attribute_type(row[field_offset], cache_field[:type])
|
440
|
+
attributes_offset+= 1
|
441
|
+
end
|
442
|
+
|
443
|
+
if attributes.length > 0
|
444
|
+
model_fields.each_with_index do |k, idx|
|
445
|
+
related_fragments[rid].add_attribute(k[0], cast_to_attribute_type(row[idx + field_offset + attributes_offset], k[1][:type]))
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
related_fragments
|
452
|
+
end
|
453
|
+
|
454
|
+
def find_records(filters, options = {})
|
455
|
+
opts = options.dup
|
456
|
+
|
457
|
+
sort_criteria = opts.fetch(:sort_criteria) { [] }
|
458
|
+
|
459
|
+
join_tree = JoinTree.new(resource_klass: self,
|
460
|
+
filters: filters,
|
461
|
+
sort_criteria: sort_criteria,
|
462
|
+
options: opts)
|
463
|
+
|
464
|
+
records, joins = apply_joins(records(opts), join_tree, opts)
|
465
|
+
|
466
|
+
opts[:joins] = joins
|
467
|
+
|
468
|
+
records = filter_records(records, filters, opts)
|
469
|
+
|
470
|
+
order_options = construct_order_options(sort_criteria)
|
471
|
+
records = sort_records(records, order_options, opts)
|
472
|
+
|
473
|
+
records = apply_pagination(records, opts[:paginator], order_options)
|
474
|
+
|
475
|
+
records.distinct
|
476
|
+
end
|
477
|
+
|
478
|
+
def get_join_alias(records, &block)
|
479
|
+
init_join_sources = records.arel.join_sources
|
480
|
+
init_join_sources_length = init_join_sources.length
|
481
|
+
|
482
|
+
records = yield(records)
|
483
|
+
|
484
|
+
join_sources = records.arel.join_sources
|
485
|
+
if join_sources.length > init_join_sources_length
|
486
|
+
last_join = (join_sources - init_join_sources).last
|
487
|
+
join_alias =
|
488
|
+
case last_join.left
|
489
|
+
when Arel::Table
|
490
|
+
last_join.left.name
|
491
|
+
when Arel::Nodes::TableAlias
|
492
|
+
last_join.left.right
|
493
|
+
when Arel::Nodes::StringJoin
|
494
|
+
# :nocov:
|
495
|
+
warn "get_join_alias: Unsupported join type - use custom filtering and sorting"
|
496
|
+
nil
|
497
|
+
# :nocov:
|
498
|
+
end
|
499
|
+
else
|
500
|
+
# :nocov:
|
501
|
+
warn "get_join_alias: No join added"
|
502
|
+
join_alias = nil
|
503
|
+
# :nocov:
|
504
|
+
end
|
505
|
+
|
506
|
+
return records, join_alias
|
507
|
+
end
|
508
|
+
|
509
|
+
def apply_joins(records, join_tree, _options)
|
510
|
+
joins = join_tree.get_joins
|
511
|
+
|
512
|
+
joins.each do |key, join_details|
|
513
|
+
case join_details[:join_type]
|
514
|
+
when :inner
|
515
|
+
records, join_alias = get_join_alias(records) { |records| records.joins(join_details[:relation_join_hash]) }
|
516
|
+
when :left
|
517
|
+
records, join_alias = get_join_alias(records) { |records| records.left_joins(join_details[:relation_join_hash]) }
|
518
|
+
end
|
519
|
+
|
520
|
+
joins[key][:alias] = join_alias
|
521
|
+
end
|
522
|
+
|
523
|
+
return records, joins
|
524
|
+
end
|
525
|
+
|
526
|
+
def apply_pagination(records, paginator, order_options)
|
527
|
+
records = paginator.apply(records, order_options) if paginator
|
528
|
+
records
|
529
|
+
end
|
530
|
+
|
531
|
+
def apply_sort(records, order_options, options)
|
532
|
+
if order_options.any?
|
533
|
+
order_options.each_pair do |field, direction|
|
534
|
+
records = apply_single_sort(records, field, direction, options)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
records
|
539
|
+
end
|
540
|
+
|
541
|
+
def apply_single_sort(records, field, direction, options)
|
542
|
+
context = options[:context]
|
543
|
+
|
544
|
+
strategy = _allowed_sort.fetch(field.to_sym, {})[:apply]
|
545
|
+
|
546
|
+
if strategy
|
547
|
+
call_method_or_proc(strategy, records, direction, context)
|
548
|
+
else
|
549
|
+
joins = options[:joins] || {}
|
550
|
+
|
551
|
+
records.order("#{get_aliased_field(field, joins, options[:related_alias])} #{direction}")
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
# Assumes ActiveRecord's counting. Override if you need a different counting method
|
556
|
+
def count_records(records)
|
557
|
+
records.count(:all)
|
558
|
+
end
|
559
|
+
|
560
|
+
def filter_records(records, filters, options)
|
561
|
+
apply_filters(records, filters, options)
|
562
|
+
end
|
563
|
+
|
564
|
+
def sort_records(records, order_options, options)
|
565
|
+
apply_sort(records, order_options, options)
|
566
|
+
end
|
567
|
+
|
568
|
+
def concat_table_field(table, field, quoted = false)
|
569
|
+
if table.blank? || field.to_s.include?('.')
|
570
|
+
# :nocov:
|
571
|
+
if quoted
|
572
|
+
"\"#{field.to_s}\""
|
573
|
+
else
|
574
|
+
field.to_s
|
575
|
+
end
|
576
|
+
# :nocov:
|
577
|
+
else
|
578
|
+
if quoted
|
579
|
+
# :nocov:
|
580
|
+
"\"#{table.to_s}\".\"#{field.to_s}\""
|
581
|
+
# :nocov:
|
582
|
+
else
|
583
|
+
"#{table.to_s}.#{field.to_s}"
|
584
|
+
end
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
def apply_filters(records, filters, options = {})
|
589
|
+
if filters
|
590
|
+
filters.each do |filter, value|
|
591
|
+
records = apply_filter(records, filter, value, options)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
records
|
596
|
+
end
|
597
|
+
|
598
|
+
def get_aliased_field(path_with_field, joins, related_alias)
|
599
|
+
relationships, relationship_path, field = parse_relationship_path(path_with_field)
|
600
|
+
relationship = relationships.last
|
601
|
+
|
602
|
+
resource_klass = relationship ? relationship.resource_klass : self
|
603
|
+
|
604
|
+
if field.empty?
|
605
|
+
field_name = resource_klass._primary_key
|
606
|
+
else
|
607
|
+
field_name = resource_klass._attribute_delegated_name(field)
|
608
|
+
end
|
609
|
+
|
610
|
+
if relationship
|
611
|
+
join_name = relationship_path
|
612
|
+
|
613
|
+
join = joins.try(:[], join_name)
|
614
|
+
|
615
|
+
table_alias = join.try(:[], :alias)
|
616
|
+
else
|
617
|
+
table_alias = related_alias
|
618
|
+
end
|
619
|
+
|
620
|
+
table_alias ||= resource_klass._table_name
|
621
|
+
|
622
|
+
concat_table_field(table_alias, field_name)
|
623
|
+
end
|
624
|
+
|
625
|
+
def apply_filter(records, filter, value, options = {})
|
626
|
+
strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply]
|
627
|
+
|
628
|
+
if strategy
|
629
|
+
records = call_method_or_proc(strategy, records, value, options)
|
630
|
+
else
|
631
|
+
joins = options[:joins] || {}
|
632
|
+
related_alias = options[:related_alias]
|
633
|
+
records = records.where(get_aliased_field(filter, joins, related_alias) => value)
|
634
|
+
end
|
635
|
+
|
636
|
+
records
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|