restful_json 3.2.2 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +272 -93
- data/Rakefile +15 -2
- data/lib/restful_json.rb +7 -9
- data/lib/restful_json/base_controller.rb +13 -0
- data/lib/restful_json/config.rb +3 -1
- data/lib/restful_json/controller.rb +324 -371
- data/lib/restful_json/default_controller.rb +12 -0
- data/lib/restful_json/railtie.rb +1 -9
- data/lib/restful_json/version.rb +1 -1
- data/lib/twinturbo/controller.rb +15 -7
- metadata +7 -13
- data/lib/application_permitter.rb +0 -93
- data/lib/restful_json/model.rb +0 -17
data/Rakefile
CHANGED
@@ -1,5 +1,18 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'bundler/setup'
|
2
|
+
require 'bundler/gem_tasks'
|
3
3
|
require 'appraisal'
|
4
|
+
|
4
5
|
require 'rspec/core/rake_task'
|
5
|
-
RSpec::Core::RakeTask.new(
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task :default do |t|
|
9
|
+
if ENV['BUNDLE_GEMFILE'] =~ /gemfiles/
|
10
|
+
exec 'rake spec'
|
11
|
+
else
|
12
|
+
Rake::Task['appraise'].execute
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task :appraise => ['appraisal:install'] do |t|
|
17
|
+
exec 'rake appraisal'
|
18
|
+
end
|
data/lib/restful_json.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
require 'restful_json/version'
|
2
2
|
require 'restful_json/config'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require 'restful_json/railtie'
|
11
|
-
#end
|
3
|
+
require 'twinturbo/application_permitter'
|
4
|
+
require 'twinturbo/controller'
|
5
|
+
require 'restful_json/base_controller'
|
6
|
+
require 'restful_json/controller'
|
7
|
+
require 'restful_json/default_controller'
|
8
|
+
|
9
|
+
ActiveSupport::Dependencies.autoload_paths << "#{Rails.root}/app/permitters" unless ActiveSupport::Dependencies.autoload_paths.include?("#{Rails.root}/app/permitters")
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RestfulJson
|
2
|
+
module BaseController
|
3
|
+
extend ::ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# <b>DEPRECATED:</b> Please use <tt>include RestfulJson::DefaultController</tt> instead.
|
7
|
+
def acts_as_restful_json
|
8
|
+
warn "[DEPRECATION] `acts_as_restful_json` is deprecated. Please use `include RestfulJson::DefaultController` or see documentation."
|
9
|
+
include ::RestfulJson::DefaultController
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/restful_json/config.rb
CHANGED
@@ -8,7 +8,8 @@ module RestfulJson
|
|
8
8
|
:predicate_prefix,
|
9
9
|
:return_resource,
|
10
10
|
:render_enabled,
|
11
|
-
:use_permitters
|
11
|
+
:use_permitters,
|
12
|
+
:avoid_respond_with
|
12
13
|
]
|
13
14
|
|
14
15
|
class << self
|
@@ -28,4 +29,5 @@ RestfulJson.configure do
|
|
28
29
|
self.return_resource = false
|
29
30
|
self.render_enabled = true
|
30
31
|
self.use_permitters = true
|
32
|
+
self.avoid_respond_with = true
|
31
33
|
end
|
@@ -22,434 +22,387 @@ module RestfulJson
|
|
22
22
|
module Controller
|
23
23
|
extend ::ActiveSupport::Concern
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
25
|
+
NILS = ['NULL'.freeze,'null'.freeze,'nil'.freeze]
|
26
|
+
SINGLE_VALUE_ACTIONS = ['create'.freeze,'update'.freeze,'destroy'.freeze,'new'.freeze]
|
27
|
+
|
28
|
+
included do
|
29
|
+
# this can be overriden in the controller via defining respond_to
|
30
|
+
formats = RestfulJson.formats || ::Mime::EXTENSION_LOOKUP.keys.collect{|m|m.to_sym}
|
31
|
+
respond_to *formats
|
32
|
+
|
33
|
+
# create class attributes for each controller option and set the value to the value in the app configuration
|
34
|
+
class_attribute :model_class, instance_writer: true
|
35
|
+
class_attribute :model_singular_name, instance_writer: true
|
36
|
+
class_attribute :model_plural_name, instance_writer: true
|
37
|
+
class_attribute :param_to_attr_and_arel_predicate, instance_writer: true
|
38
|
+
class_attribute :supported_functions, instance_writer: true
|
39
|
+
class_attribute :ordered_by, instance_writer: true
|
40
|
+
class_attribute :action_to_query, instance_writer: true
|
41
|
+
class_attribute :param_to_query, instance_writer: true
|
42
|
+
class_attribute :param_to_through, instance_writer: true
|
43
|
+
class_attribute :action_to_serializer, instance_writer: true
|
44
|
+
class_attribute :action_to_serializer_for, instance_writer: true
|
45
|
+
|
46
|
+
# use values from config
|
47
|
+
# debug uses RestfulJson.debug? because until this is done no local debug class attribute exists to check
|
48
|
+
RestfulJson::CONTROLLER_OPTIONS.each do |key|
|
49
|
+
class_attribute key, instance_writer: true
|
50
|
+
self.send("#{key}=".to_sym, RestfulJson.send(key))
|
41
51
|
end
|
42
52
|
|
53
|
+
self.param_to_attr_and_arel_predicate ||= {}
|
54
|
+
self.supported_functions ||= []
|
55
|
+
self.ordered_by ||= []
|
56
|
+
self.action_to_query ||= {}
|
57
|
+
self.param_to_query ||= {}
|
58
|
+
self.param_to_through ||= {}
|
59
|
+
self.action_to_serializer ||= {}
|
60
|
+
self.action_to_serializer_for ||= {}
|
43
61
|
end
|
44
|
-
|
45
|
-
module ActsAsRestfulJson
|
46
|
-
extend ::ActiveSupport::Concern
|
47
|
-
|
48
|
-
NILS = ['NULL','null','nil']
|
49
|
-
|
50
|
-
included do
|
51
|
-
# this can be overriden in the controller via defining respond_to
|
52
|
-
formats = RestfulJson.formats || ::Mime::EXTENSION_LOOKUP.keys.collect{|m|m.to_sym}
|
53
|
-
respond_to *formats
|
54
|
-
|
55
|
-
# create class attributes for each controller option and set the value to the value in the app configuration
|
56
|
-
class_attribute :model_class, instance_writer: true
|
57
|
-
class_attribute :model_singular_name, instance_writer: true
|
58
|
-
class_attribute :model_plural_name, instance_writer: true
|
59
|
-
class_attribute :param_to_attr_and_arel_predicate, instance_writer: true
|
60
|
-
class_attribute :supported_functions, instance_writer: true
|
61
|
-
class_attribute :ordered_by, instance_writer: true
|
62
|
-
class_attribute :action_to_query, instance_writer: true
|
63
|
-
class_attribute :param_to_query, instance_writer: true
|
64
|
-
class_attribute :param_to_through, instance_writer: true
|
65
|
-
class_attribute :action_to_serializer, instance_writer: true
|
66
|
-
|
67
|
-
# use values from config
|
68
|
-
# debug uses RestfulJson.debug? because until this is done no local debug class attribute exists to check
|
69
|
-
RestfulJson::CONTROLLER_OPTIONS.each do |key|
|
70
|
-
class_attribute key, instance_writer: true
|
71
|
-
self.send("#{key}=".to_sym, RestfulJson.send(key))
|
72
|
-
end
|
73
62
|
|
74
|
-
|
75
|
-
self.supported_functions ||= []
|
76
|
-
self.ordered_by ||= []
|
77
|
-
self.action_to_query ||= {}
|
78
|
-
self.param_to_query ||= {}
|
79
|
-
self.param_to_through ||= {}
|
80
|
-
self.action_to_serializer ||= {}
|
81
|
-
end
|
82
|
-
|
83
|
-
module ClassMethods
|
84
|
-
|
85
|
-
# A whitelist of filters and definition of filter options related to request parameters.
|
86
|
-
#
|
87
|
-
# If no options are provided or the :using option is provided, defines attributes that are queryable through the operation(s) already defined in can_filter_by_default_using, or can specify attributes:
|
88
|
-
# can_filter_by :attr_name_1, :attr_name_2 # implied using: [eq] if RestfulJson.can_filter_by_default_using = [:eq]
|
89
|
-
# can_filter_by :attr_name_1, :attr_name_2, using: [:eq, :not_eq]
|
90
|
-
#
|
91
|
-
# When :with_query is specified, it will call a supplied lambda. e.g. t is self.model_class.arel_table, q is self.model_class.scoped, and p is params[:my_param_name]:
|
92
|
-
# can_filter_by :my_param_name, with_query: ->(t,q,p) {...}
|
93
|
-
#
|
94
|
-
# When :through is specified, it will take the array supplied to through as 0 to many model names following by an attribute name. It will follow through
|
95
|
-
# each association until it gets to the attribute to filter by that via ARel joins, e.g. if the model Foobar has an association to :foo, and on the Foo model there is an assocation
|
96
|
-
# to :bar, and you want to filter by bar.name (foobar.foo.bar.name):
|
97
|
-
# can_filter_by :my_param_name, through: [:foo, :bar, :name]
|
98
|
-
def can_filter_by(*args)
|
99
|
-
options = args.extract_options!
|
100
|
-
|
101
|
-
# :using is the default action if no options are present
|
102
|
-
if options[:using] || options.size == 0
|
103
|
-
predicates = Array.wrap(options[:using] || self.can_filter_by_default_using)
|
104
|
-
predicates.each do |predicate|
|
105
|
-
predicate_sym = predicate.to_sym
|
106
|
-
args.each do |attr|
|
107
|
-
attr_sym = attr.to_sym
|
108
|
-
self.param_to_attr_and_arel_predicate[attr_sym] = [attr_sym, :eq, options] if predicate_sym == :eq
|
109
|
-
self.param_to_attr_and_arel_predicate["#{attr}#{self.predicate_prefix}#{predicate}".to_sym] = [attr_sym, predicate_sym, options]
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
if options[:with_query]
|
115
|
-
args.each do |with_query_key|
|
116
|
-
self.param_to_query[with_query_key.to_sym] = options[:with_query]
|
117
|
-
end
|
118
|
-
end
|
63
|
+
module ClassMethods
|
119
64
|
|
120
|
-
|
121
|
-
|
122
|
-
|
65
|
+
# A whitelist of filters and definition of filter options related to request parameters.
|
66
|
+
#
|
67
|
+
# If no options are provided or the :using option is provided, defines attributes that are queryable through the operation(s) already defined in can_filter_by_default_using, or can specify attributes:
|
68
|
+
# can_filter_by :attr_name_1, :attr_name_2 # implied using: [eq] if RestfulJson.can_filter_by_default_using = [:eq]
|
69
|
+
# can_filter_by :attr_name_1, :attr_name_2, using: [:eq, :not_eq]
|
70
|
+
#
|
71
|
+
# When :with_query is specified, it will call a supplied lambda. e.g. t is self.model_class.arel_table, q is self.model_class.scoped, and p is params[:my_param_name]:
|
72
|
+
# can_filter_by :my_param_name, with_query: ->(t,q,p) {...}
|
73
|
+
#
|
74
|
+
# When :through is specified, it will take the array supplied to through as 0 to many model names following by an attribute name. It will follow through
|
75
|
+
# each association until it gets to the attribute to filter by that via ARel joins, e.g. if the model Foobar has an association to :foo, and on the Foo model there is an assocation
|
76
|
+
# to :bar, and you want to filter by bar.name (foobar.foo.bar.name):
|
77
|
+
# can_filter_by :my_param_name, through: [:foo, :bar, :name]
|
78
|
+
def can_filter_by(*args)
|
79
|
+
options = args.extract_options!
|
80
|
+
|
81
|
+
# :using is the default action if no options are present
|
82
|
+
if options[:using] || options.size == 0
|
83
|
+
predicates = Array.wrap(options[:using] || self.can_filter_by_default_using)
|
84
|
+
predicates.each do |predicate|
|
85
|
+
predicate_sym = predicate.to_sym
|
86
|
+
args.each do |attr|
|
87
|
+
attr_sym = attr.to_sym
|
88
|
+
self.param_to_attr_and_arel_predicate[attr_sym] = [attr_sym, :eq, options] if predicate_sym == :eq
|
89
|
+
self.param_to_attr_and_arel_predicate["#{attr}#{self.predicate_prefix}#{predicate}".to_sym] = [attr_sym, predicate_sym, options]
|
123
90
|
end
|
124
91
|
end
|
125
92
|
end
|
126
93
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
options = args.extract_options! # overkill, sorry
|
131
|
-
self.supported_functions += args
|
132
|
-
end
|
133
|
-
|
134
|
-
# Specify a custom query. If action specified does not have a method, it will alias_method index to create a new action method with that query.
|
135
|
-
#
|
136
|
-
# t is self.model_class.arel_table and q is self.model_class.scoped, e.g.
|
137
|
-
# query_for :index, is: -> {|t,q| q.where(:status_code => 'green')}
|
138
|
-
def query_for(*args)
|
139
|
-
options = args.extract_options!
|
140
|
-
# TODO: support custom actions to be automaticaly defined
|
141
|
-
args.each do |an_action|
|
142
|
-
if options[:is]
|
143
|
-
self.action_to_query[an_action.to_s] = options[:is]
|
144
|
-
else
|
145
|
-
raise "#{self.class.name} must supply an :is option with query_for #{an_action.inspect}"
|
146
|
-
end
|
147
|
-
unless an_action.to_sym == :index
|
148
|
-
alias_method an_action.to_sym, :index
|
149
|
-
end
|
94
|
+
if options[:with_query]
|
95
|
+
args.each do |with_query_key|
|
96
|
+
self.param_to_query[with_query_key.to_sym] = options[:with_query]
|
150
97
|
end
|
151
98
|
end
|
152
99
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
# order_by {:foo_date => :asc}, :foo_color, 'foo_name', {:bar_date => :desc}
|
158
|
-
def order_by(args)
|
159
|
-
self.ordered_by = (Array.wrap(self.ordered_by) + Array.wrap(args)).flatten.compact.collect {|item|item.is_a?(Hash) ? item : {item.to_sym => :asc}}
|
100
|
+
if options[:through]
|
101
|
+
args.each do |through_key|
|
102
|
+
self.param_to_through[through_key.to_sym] = options[:through]
|
103
|
+
end
|
160
104
|
end
|
105
|
+
end
|
161
106
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
107
|
+
# Can specify additional functions in the index, e.g.
|
108
|
+
# supports_functions :skip, :uniq, :take, :count
|
109
|
+
def supports_functions(*args)
|
110
|
+
args.extract_options! # remove hash from array- we're not using it yet
|
111
|
+
self.supported_functions += args
|
112
|
+
end
|
113
|
+
|
114
|
+
# Specify a custom query. If action specified does not have a method, it will alias_method index to create a new action method with that query.
|
115
|
+
#
|
116
|
+
# t is self.model_class.arel_table and q is self.model_class.scoped, e.g.
|
117
|
+
# query_for :index, is: -> {|t,q| q.where(:status_code => 'green')}
|
118
|
+
def query_for(*args)
|
119
|
+
options = args.extract_options!
|
120
|
+
# TODO: support custom actions to be automaticaly defined
|
121
|
+
args.each do |an_action|
|
122
|
+
if options[:is]
|
123
|
+
self.action_to_query[an_action.to_s] = options[:is]
|
124
|
+
else
|
125
|
+
raise "#{self.class.name} must supply an :is option with query_for #{an_action.inspect}"
|
126
|
+
end
|
127
|
+
unless an_action.to_sym == :index
|
128
|
+
alias_method an_action.to_sym, :index
|
174
129
|
end
|
175
130
|
end
|
176
131
|
end
|
177
132
|
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
super
|
186
|
-
|
187
|
-
# if not set, use controller classname
|
188
|
-
qualified_controller_name = self.class.name.chomp('Controller')
|
189
|
-
@model_class = self.model_class || qualified_controller_name.split('::').last.singularize.constantize
|
190
|
-
|
191
|
-
raise "#{self.class.name} failed to initialize. self.model_class was nil in #{self} which shouldn't happen!" if @model_class.nil?
|
192
|
-
raise "#{self.class.name} assumes that #{self.model_class} extends ActiveRecord::Base, but it didn't. Please fix, or remove this constraint." unless @model_class.ancestors.include?(ActiveRecord::Base)
|
193
|
-
|
194
|
-
@model_singular_name = self.model_singular_name || self.model_class.name.underscore
|
195
|
-
@model_plural_name = self.model_plural_name || @model_singular_name.pluralize
|
196
|
-
@model_at_plural_name_sym = "@#{@model_plural_name}".to_sym
|
197
|
-
@model_at_singular_name_sym = "@#{@model_singular_name}".to_sym
|
198
|
-
|
199
|
-
# next 3 are for vanilla strong_parameters
|
200
|
-
@model_singular_name_params_sym = "#{@model_singular_name}_params".to_sym
|
201
|
-
@create_model_singular_name_params_sym = "create_#{@model_singular_name}_params".to_sym
|
202
|
-
@update_model_singular_name_params_sym = "update_#{@model_singular_name}_params".to_sym
|
203
|
-
|
204
|
-
underscored_modules_and_underscored_plural_model_name = qualified_controller_name.gsub('::','_').underscore
|
205
|
-
|
206
|
-
# This is a workaround for controllers that are in a different module than the model only works if the controller's base part of the unqualified name in the plural model name.
|
207
|
-
# If the model name is different than the controller name, you will need to define methods to return the right urls.
|
208
|
-
class_eval "def #{@model_plural_name}_url;#{underscored_modules_and_underscored_plural_model_name}_url;end;def #{@model_singular_name}_url(record);#{underscored_modules_and_underscored_plural_model_name.singularize}_url(record);end"
|
133
|
+
# Takes an string, symbol, array, hash to indicate order. If not a hash, assumes is ascending. Is cumulative and order defines order of sorting, e.g:
|
134
|
+
# #would order by foo_color attribute ascending
|
135
|
+
# order_by :foo_color
|
136
|
+
# or
|
137
|
+
# order_by {:foo_date => :asc}, :foo_color, 'foo_name', {:bar_date => :desc}
|
138
|
+
def order_by(args)
|
139
|
+
self.ordered_by = (Array.wrap(self.ordered_by) + Array.wrap(args)).flatten.compact.collect {|item|item.is_a?(Hash) ? item : {item.to_sym => :asc}}
|
209
140
|
end
|
210
141
|
|
211
|
-
|
212
|
-
|
142
|
+
# Associate a non-standard ActiveModel Serializer for one or more actions, e.g.
|
143
|
+
# serialize_action :index, with: FoosSerializer
|
144
|
+
# or
|
145
|
+
# serialize_action :index, :some_custom_action, with: FooSerializer
|
146
|
+
# The default functionality of each action is to use serialize for show, each, create, update, and destroy and serialize_each for index and
|
147
|
+
# any custom actions created with query_for. To override that, specify the :for option with value as :array or :each:
|
148
|
+
# serialize_action :index, :some_custom_action, with: FoosSerializer, for: :array
|
149
|
+
def serialize_action(*args)
|
150
|
+
options = args.extract_options!
|
151
|
+
args.each do |an_action|
|
152
|
+
if options[:with]
|
153
|
+
self.action_to_serializer[an_action.to_s] = options[:with]
|
154
|
+
self.action_to_serializer_for[an_action.to_s] = options[:for] if options[:for]
|
155
|
+
else
|
156
|
+
raise "#{self.class.name} must supply an :with option with serialize_action #{an_action.inspect}"
|
157
|
+
end
|
158
|
+
end
|
213
159
|
end
|
160
|
+
end
|
214
161
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
162
|
+
# In initialize we:
|
163
|
+
# * guess model name, if unspecified, from controller name
|
164
|
+
# * define instance variables containing model name
|
165
|
+
# * define the (model_plural_name)_url method, needed if controllers are not in the same module as the models
|
166
|
+
# Note: if controller name is not based on model name *and* controller is in different module than model, you'll need to
|
167
|
+
# redefine the appropriate method(s) to return urls if needed.
|
168
|
+
def initialize
|
169
|
+
super
|
170
|
+
|
171
|
+
# if not set, use controller classname
|
172
|
+
qualified_controller_name = self.class.name.chomp('Controller')
|
173
|
+
@model_class = self.model_class || qualified_controller_name.split('::').last.singularize.constantize
|
174
|
+
|
175
|
+
raise "#{self.class.name} failed to initialize. self.model_class was nil in #{self} which shouldn't happen!" if @model_class.nil?
|
176
|
+
raise "#{self.class.name} assumes that #{self.model_class} extends ActiveRecord::Base, but it didn't. Please fix, or remove this constraint." unless @model_class.ancestors.include?(ActiveRecord::Base)
|
177
|
+
|
178
|
+
@model_singular_name = self.model_singular_name || self.model_class.name.underscore
|
179
|
+
@model_plural_name = self.model_plural_name || @model_singular_name.pluralize
|
180
|
+
@model_at_plural_name_sym = "@#{@model_plural_name}".to_sym
|
181
|
+
@model_at_singular_name_sym = "@#{@model_singular_name}".to_sym
|
182
|
+
|
183
|
+
# next 3 are for vanilla strong_parameters
|
184
|
+
@model_singular_name_params_sym = "#{@model_singular_name}_params".to_sym
|
185
|
+
@create_model_singular_name_params_sym = "create_#{@model_singular_name}_params".to_sym
|
186
|
+
@update_model_singular_name_params_sym = "update_#{@model_singular_name}_params".to_sym
|
187
|
+
|
188
|
+
underscored_modules_and_underscored_plural_model_name = qualified_controller_name.gsub('::','_').underscore
|
189
|
+
|
190
|
+
# This is a workaround for controllers that are in a different module than the model only works if the controller's base part of the unqualified name in the plural model name.
|
191
|
+
# If the model name is different than the controller name, you will need to define methods to return the right urls.
|
192
|
+
class_eval "def #{@model_plural_name}_url;#{underscored_modules_and_underscored_plural_model_name}_url;end" unless @model_plural_name == underscored_modules_and_underscored_plural_model_name
|
193
|
+
singularized_underscored_modules_and_underscored_plural_model_name = underscored_modules_and_underscored_plural_model_name
|
194
|
+
class_eval "def #{@model_singular_name}_url(record);#{singularized_underscored_modules_and_underscored_plural_model_name}_url(record);end" unless @model_singular_name == singularized_underscored_modules_and_underscored_plural_model_name
|
195
|
+
end
|
225
196
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
value = param_query.call(t, value, params[param_name].to_s)
|
230
|
-
end
|
231
|
-
end
|
197
|
+
def convert_request_param_value_for_filtering(attr_sym, value)
|
198
|
+
value && NILS.include?(value) ? nil : value
|
199
|
+
end
|
232
200
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
201
|
+
def render_or_respond(read_only_action, success_code = :ok)
|
202
|
+
if self.render_enabled
|
203
|
+
if !@value.nil? && ((read_only_action && RestfulJson.return_resource) || RestfulJson.avoid_respond_with)
|
204
|
+
respond_with(@value) do |format|
|
205
|
+
format.json do
|
206
|
+
# define local variables in blocks, not outside of them, to be safe, even though would work in this case
|
207
|
+
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
208
|
+
custom_action_serializer_for = self.action_to_serializer_for[params[:action].to_s]
|
209
|
+
serialization_key = single_value_response? ? (custom_action_serializer_for == :each ? :each_serializer : :serializer) : (custom_action_serializer_for == :array ? :serializer : :each_serializer)
|
210
|
+
puts "serialization key = #{serialization_key}, custom_action_serializer = #{custom_action_serializer}"
|
211
|
+
if !@value.respond_to?(:errors) || @value.errors.empty?
|
212
|
+
render custom_action_serializer ? {json: @value, status: success_code, serialization_key => custom_action_serializer} : {json: @value, status: success_code}
|
244
213
|
else
|
245
|
-
|
246
|
-
if found_classes.size > 0
|
247
|
-
last_model_class = found_classes[0]
|
248
|
-
else
|
249
|
-
# bad can_filter_by :through found at runtime
|
250
|
-
raise "Association #{association_or_attribute.inspect} not found on #{last_model_class}."
|
251
|
-
end
|
252
|
-
|
253
|
-
if joins.nil?
|
254
|
-
joins = association_or_attribute
|
255
|
-
else
|
256
|
-
joins = {association_or_attribute => joins}
|
257
|
-
end
|
214
|
+
render custom_action_serializer ? {json: {errors: @value.errors}, status: :unprocessable_entity, serialization_key => custom_action_serializer} : {json: {errors: @value.errors}, status: :unprocessable_entity}
|
258
215
|
end
|
259
216
|
end
|
260
217
|
end
|
218
|
+
else
|
219
|
+
# code duplicated from above because local vars don't always traverse well into block (based on wierd ruby-proc bug experienced)
|
220
|
+
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
221
|
+
custom_action_serializer_for = self.action_to_serializer_for[params[:action].to_s]
|
222
|
+
serialization_key = single_value_response? ? (custom_action_serializer_for == :array ? :serializer : :each_serializer) : (custom_action_serializer_for == :each ? :each_serializer : :serializer)
|
223
|
+
respond_with @value, custom_action_serializer ? {(self.action_to_serializer_for[params[:action].to_s] == :each ? :each_serializer : :serializer) => custom_action_serializer} : {}
|
261
224
|
end
|
225
|
+
else
|
226
|
+
@value
|
227
|
+
end
|
228
|
+
end
|
262
229
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
param = params[param_name].to_s || options[:with_default]
|
267
|
-
|
268
|
-
if param.present? && param_to_attr_and_arel_predicate[param_name]
|
269
|
-
attr_sym = param_to_attr_and_arel_predicate[param_name][0]
|
270
|
-
predicate_sym = param_to_attr_and_arel_predicate[param_name][1]
|
271
|
-
if predicate_sym == :eq
|
272
|
-
value = value.where(attr_sym => convert_request_param_value_for_filtering(attr_sym, param))
|
273
|
-
else
|
274
|
-
one_or_more_param = param.split(self.filter_split).collect{|v|convert_request_param_value_for_filtering(attr_sym, v)}
|
275
|
-
value = value.where(t[attr_sym].try(predicate_sym, one_or_more_param))
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
if params[:page] && self.supported_functions.include?(:page)
|
281
|
-
page = params[:page].to_i
|
282
|
-
page = 1 if page < 1 # to avoid people using this as a way to get all records unpaged, as that probably isn't the intent?
|
283
|
-
#TODO: to_s is hack to avoid it becoming an Arel::SelectManager for some reason which not sure what to do with
|
284
|
-
value = value.skip((self.number_of_records_in_a_page * (page - 1)).to_s)
|
285
|
-
value = value.take((self.number_of_records_in_a_page).to_s)
|
286
|
-
end
|
230
|
+
def single_value_response?
|
231
|
+
SINGLE_VALUE_ACTIONS.include?(params[:action].to_s)
|
232
|
+
end
|
287
233
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
234
|
+
# The controller's index (list) method to list resources.
|
235
|
+
#
|
236
|
+
# Note: this method be alias_method'd by query_for, so it is more than just index.
|
237
|
+
def index
|
238
|
+
t = @model_class.arel_table
|
239
|
+
value = @model_class.scoped # returns ActiveRecord::Relation equivalent to select with no where clause
|
240
|
+
custom_query = self.action_to_query[params[:action].to_s]
|
241
|
+
if custom_query
|
242
|
+
value = custom_query.call(t, value)
|
243
|
+
end
|
292
244
|
|
293
|
-
|
245
|
+
self.param_to_query.each do |param_name, param_query|
|
246
|
+
if params[param_name]
|
294
247
|
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
295
|
-
value =
|
248
|
+
value = param_query.call(t, value, params[param_name].to_s)
|
296
249
|
end
|
250
|
+
end
|
297
251
|
|
298
|
-
|
299
|
-
|
300
|
-
|
252
|
+
self.param_to_through.each do |param_name, through_array|
|
253
|
+
if params[param_name]
|
254
|
+
# build query
|
255
|
+
# e.g. SomeModel.scoped.joins({:assoc_name => {:sub_assoc => {:sub_sub_assoc => :sub_sub_sub_assoc}}).where(sub_sub_sub_assoc_model_table_name: {column_name: value})
|
256
|
+
last_model_class = @model_class
|
257
|
+
joins = nil # {:assoc_name => {:sub_assoc => {:sub_sub_assoc => :sub_sub_sub_assoc}}
|
258
|
+
through_array.each do |association_or_attribute|
|
259
|
+
if association_or_attribute == through_array.last
|
260
|
+
# must convert param value to string before possibly using with ARel because of CVE-2013-1854, fixed in: 3.2.13 and 3.1.12
|
261
|
+
# https://groups.google.com/forum/?fromgroups=#!msg/rubyonrails-security/jgJ4cjjS8FE/BGbHRxnDRTIJ
|
262
|
+
value = value.joins(joins).where(last_model_class.table_name.to_sym => {association_or_attribute => params[param_name].to_s})
|
263
|
+
else
|
264
|
+
found_classes = last_model_class.reflections.collect {|association_name, reflection| reflection.class_name.constantize if association_name.to_sym == association_or_attribute}.compact
|
265
|
+
if found_classes.size > 0
|
266
|
+
last_model_class = found_classes[0]
|
267
|
+
else
|
268
|
+
# bad can_filter_by :through found at runtime
|
269
|
+
raise "Association #{association_or_attribute.inspect} not found on #{last_model_class}."
|
270
|
+
end
|
301
271
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
else
|
309
|
-
self.ordered_by.each do |attr_to_direction|
|
310
|
-
# this looks nasty, but makes no sense to iterate keys if only single of each
|
311
|
-
value = value.order(t[attr_to_direction.keys[0]].send(attr_to_direction.values[0]))
|
272
|
+
if joins.nil?
|
273
|
+
joins = association_or_attribute
|
274
|
+
else
|
275
|
+
joins = {association_or_attribute => joins}
|
276
|
+
end
|
277
|
+
end
|
312
278
|
end
|
313
|
-
value = value.to_a
|
314
|
-
end
|
315
|
-
|
316
|
-
@value = value
|
317
|
-
instance_variable_set(@model_at_plural_name_sym, @value)
|
318
|
-
|
319
|
-
if self.render_enabled
|
320
|
-
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
321
|
-
respond_with @value, custom_action_serializer ? {serializer: custom_action_serializer} : {}
|
322
|
-
else
|
323
|
-
@value
|
324
279
|
end
|
325
280
|
end
|
326
281
|
|
327
|
-
|
328
|
-
|
329
|
-
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
282
|
+
self.param_to_attr_and_arel_predicate.keys.each do |param_name|
|
283
|
+
options = param_to_attr_and_arel_predicate[param_name][2]
|
284
|
+
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
285
|
+
param = params[param_name].to_s || options[:with_default]
|
286
|
+
|
287
|
+
if param.present? && param_to_attr_and_arel_predicate[param_name]
|
288
|
+
attr_sym = param_to_attr_and_arel_predicate[param_name][0]
|
289
|
+
predicate_sym = param_to_attr_and_arel_predicate[param_name][1]
|
290
|
+
if predicate_sym == :eq
|
291
|
+
value = value.where(attr_sym => convert_request_param_value_for_filtering(attr_sym, param))
|
292
|
+
else
|
293
|
+
one_or_more_param = param.split(self.filter_split).collect{|v|convert_request_param_value_for_filtering(attr_sym, v)}
|
294
|
+
value = value.where(t[attr_sym].try(predicate_sym, one_or_more_param))
|
295
|
+
end
|
338
296
|
end
|
339
297
|
end
|
340
298
|
|
341
|
-
|
342
|
-
|
343
|
-
|
299
|
+
if params[:page] && self.supported_functions.include?(:page)
|
300
|
+
page = params[:page].to_i
|
301
|
+
page = 1 if page < 1 # to avoid people using this as a way to get all records unpaged, as that probably isn't the intent?
|
302
|
+
#TODO: to_s is hack to avoid it becoming an Arel::SelectManager for some reason which not sure what to do with
|
303
|
+
value = value.skip((self.number_of_records_in_a_page * (page - 1)).to_s)
|
304
|
+
value = value.take((self.number_of_records_in_a_page).to_s)
|
305
|
+
end
|
344
306
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
else
|
349
|
-
@value
|
350
|
-
end
|
307
|
+
if params[:skip] && self.supported_functions.include?(:skip)
|
308
|
+
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
309
|
+
value = value.skip(params[:skip].to_s)
|
351
310
|
end
|
352
311
|
|
353
|
-
|
354
|
-
def edit
|
312
|
+
if params[:take] && self.supported_functions.include?(:take)
|
355
313
|
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
356
|
-
|
357
|
-
instance_variable_set(@model_at_singular_name_sym, @value)
|
358
|
-
@value
|
314
|
+
value = value.take(params[:take].to_s)
|
359
315
|
end
|
360
316
|
|
361
|
-
|
362
|
-
|
363
|
-
if self.use_permitters
|
364
|
-
authorize! :create, @model_class
|
365
|
-
allowed_params = permitted_params
|
366
|
-
elsif respond_to? @create_model_singular_name_params_sym
|
367
|
-
allowed_params = send(@create_model_singular_name_params_sym)
|
368
|
-
elsif respond_to? @model_singular_name_params_sym
|
369
|
-
allowed_params = send(@model_singular_name_params_sym)
|
370
|
-
else
|
371
|
-
allowed_params = params
|
372
|
-
end
|
373
|
-
@value = @model_class.new(allowed_params)
|
374
|
-
@value.save
|
375
|
-
instance_variable_set(@model_at_singular_name_sym, @value)
|
376
|
-
|
377
|
-
if self.render_enabled
|
378
|
-
if !@value.nil? && RestfulJson.return_resource
|
379
|
-
respond_with(@value) do |format|
|
380
|
-
format.json do
|
381
|
-
# define local variables in blocks, not outside of them, to be safe, even though would work in this case
|
382
|
-
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
383
|
-
if @value.errors.empty?
|
384
|
-
render custom_action_serializer ? {json: @value, status: :created, serializer: custom_action_serializer} : {json: @value, status: :created}
|
385
|
-
else
|
386
|
-
render custom_action_serializer ? {json: {errors: @value.errors}, status: :unprocessable_entity, serializer: custom_action_serializer} : {json: {errors: @value.errors}, status: :unprocessable_entity}
|
387
|
-
end
|
388
|
-
end
|
389
|
-
end
|
390
|
-
else
|
391
|
-
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
392
|
-
respond_with @value, custom_action_serializer ? {serializer: custom_action_serializer} : {}
|
393
|
-
end
|
394
|
-
else
|
395
|
-
@value
|
396
|
-
end
|
317
|
+
if params[:uniq] && self.supported_functions.include?(:uniq)
|
318
|
+
value = value.uniq
|
397
319
|
end
|
398
320
|
|
399
|
-
#
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
allowed_params = params
|
410
|
-
end
|
411
|
-
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
412
|
-
@value = @model_class.find(params[:id].to_s)
|
413
|
-
@value.update_attributes(allowed_params)
|
414
|
-
instance_variable_set(@model_at_singular_name_sym, @value)
|
415
|
-
|
416
|
-
if self.render_enabled
|
417
|
-
if !@value.nil? && RestfulJson.return_resource
|
418
|
-
respond_with(@value) do |format|
|
419
|
-
format.json do
|
420
|
-
# define local variables in blocks, not outside of them, to be safe, even though would work in this case
|
421
|
-
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
422
|
-
if @value.errors.empty?
|
423
|
-
render custom_action_serializer ? {json: @value, status: :ok, serializer: custom_action_serializer} : {json: @value, status: :ok}
|
424
|
-
else
|
425
|
-
render custom_action_serializer ? {json: {errors: @value.errors}, status: :unprocessable_entity, serializer: custom_action_serializer} : {json: {errors: @value.errors}, status: :unprocessable_entity}
|
426
|
-
end
|
427
|
-
end
|
428
|
-
end
|
429
|
-
else
|
430
|
-
custom_action_serializer = self.action_to_serializer[params[:action].to_s]
|
431
|
-
respond_with @value, custom_action_serializer ? {serializer: custom_action_serializer} : {}
|
432
|
-
end
|
433
|
-
else
|
434
|
-
@value
|
321
|
+
# these must happen at the end and are independent
|
322
|
+
if params[:count] && self.supported_functions.include?(:count)
|
323
|
+
value = value.count.to_i
|
324
|
+
elsif params[:page_count] && self.supported_functions.include?(:page_count)
|
325
|
+
count_value = value.count.to_i # this executes the query so nothing else can be done in AREL
|
326
|
+
value = (count_value / self.number_of_records_in_a_page) + (count_value % self.number_of_records_in_a_page ? 1 : 0)
|
327
|
+
else
|
328
|
+
self.ordered_by.each do |attr_to_direction|
|
329
|
+
# this looks nasty, but makes no sense to iterate keys if only single of each
|
330
|
+
value = value.order(t[attr_to_direction.keys[0]].send(attr_to_direction.values[0]))
|
435
331
|
end
|
332
|
+
value = value.to_a
|
436
333
|
end
|
437
334
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
@value.destroy
|
443
|
-
instance_variable_set(@model_at_singular_name_sym, @value)
|
335
|
+
@value = value
|
336
|
+
instance_variable_set(@model_at_plural_name_sym, @value)
|
337
|
+
render_or_respond(true)
|
338
|
+
end
|
444
339
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
340
|
+
# The controller's show (get) method to return a resource.
|
341
|
+
def show
|
342
|
+
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
343
|
+
@value = @model_class.find(params[:id].to_s)
|
344
|
+
instance_variable_set(@model_at_singular_name_sym, @value)
|
345
|
+
render_or_respond(true)
|
346
|
+
end
|
347
|
+
|
348
|
+
# The controller's new method (e.g. used for new record in html format).
|
349
|
+
def new
|
350
|
+
@value = @model_class.new
|
351
|
+
render_or_respond(true)
|
352
|
+
end
|
353
|
+
|
354
|
+
# The controller's edit method (e.g. used for edit record in html format).
|
355
|
+
def edit
|
356
|
+
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
357
|
+
@value = @model_class.find(params[:id].to_s)
|
358
|
+
instance_variable_set(@model_at_singular_name_sym, @value)
|
359
|
+
@value
|
360
|
+
end
|
361
|
+
|
362
|
+
# The controller's create (post) method to create a resource.
|
363
|
+
def create
|
364
|
+
if self.use_permitters
|
365
|
+
authorize! :create, @model_class
|
366
|
+
allowed_params = permitted_params
|
367
|
+
elsif respond_to? @create_model_singular_name_params_sym
|
368
|
+
allowed_params = send(@create_model_singular_name_params_sym)
|
369
|
+
elsif respond_to? @model_singular_name_params_sym
|
370
|
+
allowed_params = send(@model_singular_name_params_sym)
|
371
|
+
else
|
372
|
+
allowed_params = params
|
451
373
|
end
|
374
|
+
@value = @model_class.new(allowed_params)
|
375
|
+
@value.save
|
376
|
+
instance_variable_set(@model_at_singular_name_sym, @value)
|
377
|
+
render_or_respond(false, :created)
|
378
|
+
end
|
379
|
+
|
380
|
+
# The controller's update (put) method to update a resource.
|
381
|
+
def update
|
382
|
+
if self.use_permitters
|
383
|
+
authorize! :update, @model_class
|
384
|
+
allowed_params = permitted_params
|
385
|
+
elsif respond_to? @create_model_singular_name_params_sym
|
386
|
+
allowed_params = send(@update_model_singular_name_params_sym)
|
387
|
+
elsif respond_to? @model_singular_name_params_sym
|
388
|
+
allowed_params = send(@model_singular_name_params_sym)
|
389
|
+
else
|
390
|
+
allowed_params = params
|
391
|
+
end
|
392
|
+
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
393
|
+
@value = @model_class.find(params[:id].to_s)
|
394
|
+
@value.update_attributes(allowed_params)
|
395
|
+
instance_variable_set(@model_at_singular_name_sym, @value)
|
396
|
+
render_or_respond(false)
|
397
|
+
end
|
452
398
|
|
399
|
+
# The controller's destroy (delete) method to destroy a resource.
|
400
|
+
def destroy
|
401
|
+
# to_s as safety measure for vulnerabilities similar to CVE-2013-1854
|
402
|
+
@value = @model_class.find(params[:id].to_s)
|
403
|
+
@value.destroy
|
404
|
+
instance_variable_set(@model_at_singular_name_sym, @value)
|
405
|
+
render_or_respond(false)
|
453
406
|
end
|
454
407
|
end
|
455
408
|
end
|