restful_json 3.2.2 → 3.3.0
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 +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
|