apique 1.0.0 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|