apique 1.0.0 → 1.1.2
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/lib/apique/basics.rb +6 -6
- data/lib/apique/filterable.rb +85 -21
- data/lib/apique/listable.rb +42 -5
- data/lib/apique/pickable.rb +8 -2
- data/lib/apique/sortable.rb +1 -1
- data/lib/apique/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ee5c5d499ded1ebb2f79cdc449d84223d09c9b6
|
4
|
+
data.tar.gz: d0d2b475eb89a5aec8f5f571fb471548dd03f299
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27dc8bfbfbf4dd6571865895d3da2fc6815b7db7544726e5f706d6515e01a77f9e7f9842ccdb19cd66ca4868b307d7d273ca487901abf1d37974730cc6c46d47
|
7
|
+
data.tar.gz: b1fe4d5183fb1ee8fcd599c8f107bd171217848c97b12418ce7b087f6acbf114af7399222fd87105719371257a575b5480ed33fb6924865a6b5b331f52d5b125
|
data/lib/apique/basics.rb
CHANGED
@@ -3,12 +3,12 @@ module Apique::Basics
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
protect_from_forgery with: :null_session
|
7
|
-
|
8
6
|
before_action :authorize_access
|
9
7
|
|
10
|
-
|
11
|
-
|
8
|
+
if defined? CanCan
|
9
|
+
rescue_from CanCan::AccessDenied do |e|
|
10
|
+
render json: {error: e.message}, status: :forbidden
|
11
|
+
end
|
12
12
|
end
|
13
13
|
|
14
14
|
rescue_from NotImplementedError do |e|
|
@@ -39,13 +39,13 @@ module Apique::Basics
|
|
39
39
|
# The plural name of the ORM class.
|
40
40
|
# @return [String]
|
41
41
|
def collection_name
|
42
|
-
@collection_name ||= params[:model]
|
42
|
+
@collection_name ||= params[:model] || controller_name.underscore.tr('/', '~')
|
43
43
|
end
|
44
44
|
|
45
45
|
# The ORM class of the resource.
|
46
46
|
# @return [Class]
|
47
47
|
def resource_class
|
48
|
-
@resource_class ||= collection_name.singularize.classify.constantize
|
48
|
+
@resource_class ||= collection_name.singularize.tr('~', '/').classify.constantize
|
49
49
|
end
|
50
50
|
|
51
51
|
end
|
data/lib/apique/filterable.rb
CHANGED
@@ -15,6 +15,9 @@ module Apique::Filterable
|
|
15
15
|
params_types['q'] = [String, Hash]
|
16
16
|
end
|
17
17
|
|
18
|
+
UUID_RE = /^[a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}$/
|
19
|
+
BOOL_RE = /^(true|false|t|f)$/
|
20
|
+
ISO8601_RE = /^\d{4}(-\d\d){2}T\d\d(:\d\d){2}(\.\d+)?Z([+-]\d\d:\d\d)?$/
|
18
21
|
|
19
22
|
private
|
20
23
|
|
@@ -58,29 +61,55 @@ module Apique::Filterable
|
|
58
61
|
# A call to a scope. A method must be defined as a scope to work on another ORM relation.
|
59
62
|
collection = collection.public_send "search_by_#{k}", v
|
60
63
|
else
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
64
|
+
conditions = k.split('_or_').map do |k|
|
65
|
+
case get_cast_type(k)
|
66
|
+
when :text
|
67
|
+
if v.is_a? Array
|
68
|
+
["#{k} IN (?)", v.select(&:present?)]
|
69
|
+
else
|
70
|
+
["#{k} ILIKE ?", "%#{v}%"]
|
71
|
+
end
|
72
|
+
when :integer
|
73
|
+
if v.is_a? Hash
|
74
|
+
[:from, :to].each {|x| v[x] &&= v[x].to_i}
|
75
|
+
from_to_statement(k, v)
|
76
|
+
elsif enum_map = resource_class.defined_enums[k.to_s]
|
77
|
+
if v.is_a? Array
|
78
|
+
["#{k} IN (?)", v.map {|vi| enum_map[vi]}]
|
79
|
+
else
|
80
|
+
["#{k} = ?", enum_map[v]]
|
81
|
+
end
|
82
|
+
else
|
83
|
+
if v.is_a? Array
|
84
|
+
["#{k} IN (?)", v.map(&:to_i)]
|
85
|
+
else
|
86
|
+
["#{k} = ?", v.to_i]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
when :uuid
|
90
|
+
if v.is_a? Array
|
91
|
+
["#{k} IN (?)", v.select {|vi| v =~ UUID_RE}]
|
92
|
+
else
|
93
|
+
if v =~ UUID_RE
|
94
|
+
["#{k} = ?", v]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
when :boolean
|
98
|
+
if v =~ BOOL_RE
|
99
|
+
["#{k} = ?", v]
|
100
|
+
end
|
101
|
+
when :datetime
|
102
|
+
[:from, :to].each {|x| v.delete x if v[x] !~ ISO8601_RE}
|
103
|
+
from_to_statement(k, v)
|
82
104
|
end
|
105
|
+
end.compact
|
106
|
+
|
107
|
+
if conditions.blank?
|
108
|
+
collection = collection.none
|
109
|
+
break
|
83
110
|
end
|
111
|
+
|
112
|
+
collection = collection.where conditions.map(&:first)*' OR ', *conditions.map(&:last)
|
84
113
|
end
|
85
114
|
end
|
86
115
|
|
@@ -88,4 +117,39 @@ module Apique::Filterable
|
|
88
117
|
end
|
89
118
|
end
|
90
119
|
|
120
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
121
|
+
def get_cast_type_class(field)
|
122
|
+
resource_class.columns.find {|i| i.name == field}.instance_variable_get(:@cast_type).class
|
123
|
+
end
|
124
|
+
else
|
125
|
+
def get_cast_type_class(field)
|
126
|
+
resource_class.columns.find {|i| i.name == field}.cast_type
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_cast_type(field)
|
131
|
+
type = get_cast_type_class(field)
|
132
|
+
if [ActiveModel::Type::String, ActiveModel::Type::Text].find {|k| type <= k}
|
133
|
+
:text
|
134
|
+
elsif type <= ActiveModel::Type::Integer
|
135
|
+
:integer
|
136
|
+
elsif type <= ActiveModel::Type::Boolean
|
137
|
+
:boolean
|
138
|
+
elsif type <= ActiveModel::Type::DateTime
|
139
|
+
:datetime
|
140
|
+
elsif type <= ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid
|
141
|
+
:uuid
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def from_to_statement(k, v)
|
146
|
+
if v[:from].present? and v[:to].present?
|
147
|
+
["#{k} BETWEEN ? AND ?", v[:from], v[:to]]
|
148
|
+
elsif v[:from].present?
|
149
|
+
["#{k} >= ?", v[:from]]
|
150
|
+
elsif v[:to].present?
|
151
|
+
["#{k} <= ?", v[:to]]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
91
155
|
end
|
data/lib/apique/listable.rb
CHANGED
@@ -7,9 +7,17 @@ module Apique::Listable
|
|
7
7
|
# [Hash: param_key_string => {:desc, :example}]
|
8
8
|
class_attribute :params_usage
|
9
9
|
self.params_usage = {}
|
10
|
+
params_usage['f'] = {
|
11
|
+
desc: "f [String] serialize the collection in a specific format (optional)"
|
12
|
+
}
|
13
|
+
params_usage['j'] = {
|
14
|
+
desc: "j [Array<String>] join a set of relations (optional)"
|
15
|
+
}
|
10
16
|
# [Hash: param_key_string => param_types_array]
|
11
17
|
class_attribute :params_types
|
12
18
|
self.params_types = {}
|
19
|
+
params_types['f'] = [String]
|
20
|
+
params_types['j'] = [Array]
|
13
21
|
|
14
22
|
helper_method :subject_collection
|
15
23
|
|
@@ -23,7 +31,7 @@ module Apique::Listable
|
|
23
31
|
|
24
32
|
# Exception will raise if a request query would contain an incorrect of malformed parameter.
|
25
33
|
# This will provide the front-end with a description of the correct API usage.
|
26
|
-
class Apique::MalformedParameters < Exception
|
34
|
+
class ::Apique::MalformedParameters < Exception
|
27
35
|
|
28
36
|
def initialize(request, param, params_usage, *args)
|
29
37
|
message = "Bad request query: malformed parameter #{param}"
|
@@ -48,6 +56,7 @@ module Apique::Listable
|
|
48
56
|
|
49
57
|
# GET /api/{plural_resource_name}
|
50
58
|
def index
|
59
|
+
join_relations!
|
51
60
|
if respond_to? :filter_collection!, true
|
52
61
|
filter_collection!
|
53
62
|
end
|
@@ -57,18 +66,24 @@ module Apique::Listable
|
|
57
66
|
if respond_to? :paginate_collection!, true
|
58
67
|
paginate_collection!
|
59
68
|
end
|
60
|
-
render json: subject_collection
|
69
|
+
render json: subject_collection, apique_format: params[:f] || 'default'
|
61
70
|
end
|
62
71
|
|
63
72
|
|
64
73
|
private
|
65
74
|
|
66
75
|
### Basic getters-setters ###
|
76
|
+
|
77
|
+
# The name of ivar to cache the filtered collection.
|
78
|
+
# @return [String]
|
79
|
+
def collection_variable_name
|
80
|
+
@collection_variable_name ||= "@#{collection_name.tr('~', '_')}"
|
81
|
+
end
|
67
82
|
|
68
83
|
# Returns the collection from the created instance variable.
|
69
84
|
# @return [DB collection proxy]
|
70
85
|
def get_collection
|
71
|
-
instance_variable_get(
|
86
|
+
instance_variable_get(collection_variable_name) || set_collection
|
72
87
|
end
|
73
88
|
|
74
89
|
# Used as a helper to create DRYed json builders in inheritable controllers.
|
@@ -77,13 +92,20 @@ module Apique::Listable
|
|
77
92
|
# Puts the collection into an instance variable.
|
78
93
|
# In most cases, the collection exactly allowed for the user by CanCan rules should be an entry point.
|
79
94
|
# @return [DB collection proxy]
|
80
|
-
def set_collection(collection = resource_class.accessible_by(current_ability))
|
81
|
-
instance_variable_set(
|
95
|
+
def set_collection(collection = defined?(CanCan) ? resource_class.accessible_by(current_ability) : resource_class.all)
|
96
|
+
instance_variable_set(collection_variable_name, collection)
|
82
97
|
end
|
83
98
|
|
84
99
|
|
85
100
|
### Whitelists ###
|
86
101
|
|
102
|
+
# @virtual
|
103
|
+
# Pattern method for relations a user can join. Add whitelisting logic in a descendant using `super`.
|
104
|
+
# @return [ActionController::Parameters]
|
105
|
+
def join_params
|
106
|
+
params[:j].presence || ActionController::Parameters.new
|
107
|
+
end
|
108
|
+
|
87
109
|
# Check consistency of a query parameters for a list. Use it as a before filter if needed.
|
88
110
|
# @raise [MalformedParameters] if the request query is malformed.
|
89
111
|
def validate_query
|
@@ -100,4 +122,19 @@ module Apique::Listable
|
|
100
122
|
end
|
101
123
|
end
|
102
124
|
|
125
|
+
|
126
|
+
### Collection filters ###
|
127
|
+
|
128
|
+
# Join (eager load) to the current collection the references specified by params[:j]
|
129
|
+
# @return [DB collection proxy]
|
130
|
+
def join_relations!
|
131
|
+
collection = get_collection
|
132
|
+
|
133
|
+
if join_params.present?
|
134
|
+
collection = collection.includes(*join_params)
|
135
|
+
end
|
136
|
+
|
137
|
+
set_collection collection
|
138
|
+
end
|
139
|
+
|
103
140
|
end
|
data/lib/apique/pickable.rb
CHANGED
@@ -31,11 +31,17 @@ module Apique::Pickable
|
|
31
31
|
def resource_name
|
32
32
|
@resource_name ||= collection_name.singularize
|
33
33
|
end
|
34
|
+
|
35
|
+
# The name of ivar to cache the picked resource.
|
36
|
+
# @return [String]
|
37
|
+
def resource_variable_name
|
38
|
+
@resource_variable_name ||= "@#{resource_name.tr('~', '_')}"
|
39
|
+
end
|
34
40
|
|
35
41
|
# Returns the resource from the created instance variable.
|
36
42
|
# @return [Object]
|
37
43
|
def get_resource
|
38
|
-
instance_variable_get(
|
44
|
+
instance_variable_get(resource_variable_name) || set_resource
|
39
45
|
end
|
40
46
|
|
41
47
|
# Used as a helper to create DRYed json builders in inheritable controllers.
|
@@ -50,7 +56,7 @@ module Apique::Pickable
|
|
50
56
|
raise RecordNotFound, "could not find a record of type #{resource_class} with id = #{params[:id]}"
|
51
57
|
end
|
52
58
|
|
53
|
-
instance_variable_set(
|
59
|
+
instance_variable_set(resource_variable_name, resource)
|
54
60
|
end
|
55
61
|
|
56
62
|
end
|
data/lib/apique/sortable.rb
CHANGED
data/lib/apique/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apique
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Baev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|