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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c185ec939c0e1327ab39f7a28e5c0d35c3638eb6
4
- data.tar.gz: 446dbe26222728a725f071ff1682a40090f0178a
3
+ metadata.gz: 5ee5c5d499ded1ebb2f79cdc449d84223d09c9b6
4
+ data.tar.gz: d0d2b475eb89a5aec8f5f571fb471548dd03f299
5
5
  SHA512:
6
- metadata.gz: f522d9cedae7c11e5b1dbe3cf275435bb32141398c9648963352da0cb94daafff6d97918a3dbc99d9c4819f1e000d3dfc24948a332ce24d23944f6316f69ee20
7
- data.tar.gz: 0194de1b634a6045f269945e9851c106ecde80358632bcb3f038bfb680547c02cf3ca7ec8b81098e727335429d52ead576350d9bf2bec511b3a4e9c81f439f84
6
+ metadata.gz: 27dc8bfbfbf4dd6571865895d3da2fc6815b7db7544726e5f706d6515e01a77f9e7f9842ccdb19cd66ca4868b307d7d273ca487901abf1d37974730cc6c46d47
7
+ data.tar.gz: b1fe4d5183fb1ee8fcd599c8f107bd171217848c97b12418ce7b087f6acbf114af7399222fd87105719371257a575b5480ed33fb6924865a6b5b331f52d5b125
@@ -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
- rescue_from CanCan::AccessDenied do |e|
11
- render json: {error: e.message}, status: :forbidden
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
@@ -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
- is_text = [ActiveRecord::Type::String, ActiveRecord::Type::Text].include? resource_class.columns.find {|i| i.name == k}.cast_type
62
-
63
- if k =~ /_or_/
64
- fields = k.split('_or_')
65
-
66
- if is_text
67
- collection = collection.where [
68
- fields.map {|i| "#{i} ILIKE ?"} * ' OR ',
69
- *(["%#{v}%"] * fields.size)
70
- ]
71
- else
72
- collection = collection.where [
73
- fields.map {|i| "#{i} = ?"} * ' OR ',
74
- *([v] * fields.size)
75
- ]
76
- end
77
- else
78
- if is_text
79
- collection = collection.where ["#{k} ILIKE ?", "%#{v}%"]
80
- else
81
- collection = collection.where k => v
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
@@ -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("@#{collection_name}") || set_collection
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("@#{collection_name}", collection)
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
@@ -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("@#{resource_name}") || set_resource
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("@#{resource_name}", resource)
59
+ instance_variable_set(resource_variable_name, resource)
54
60
  end
55
61
 
56
62
  end
@@ -41,7 +41,7 @@ module Apique::Sortable
41
41
  end
42
42
  end
43
43
  if collection.order_values.blank?
44
- collection = collection.order "id desc"
44
+ collection = collection.order "#{resource_class.primary_key} desc"
45
45
  end
46
46
 
47
47
  set_collection collection
@@ -1,3 +1,3 @@
1
1
  module Apique
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.2"
3
3
  end
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.0.0
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: 2016-04-23 00:00:00.000000000 Z
11
+ date: 2017-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler