Module: RestfulJson::Controller
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/restful_json/controller.rb
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- NILS =
['NULL'.freeze, 'null'.freeze, 'nil'.freeze]
- SINGLE_VALUE_ACTIONS =
[:create, :update, :destroy, :show, :new, :edit]
Instance Method Summary collapse
-
#additional_render_or_respond_success_options ⇒ Object
Returns additional rendering options.
- #allowed_params ⇒ Object
- #apply_includes(value) ⇒ Object
- #convert_request_param_value_for_filtering(attr_sym, value) ⇒ Object
-
#create ⇒ Object
The controller’s create (post) method to create a resource.
- #current_action_includes ⇒ Object
- #default_additional_render_or_respond_success_options ⇒ Object
-
#destroy ⇒ Object
The controller’s destroy (delete) method to destroy a resource.
- #do_find_model_instance(first_method) ⇒ Object
-
#edit ⇒ Object
The controller’s edit method (e.g. used for edit record in html format).
-
#exception_handling_data(e) ⇒ Object
Searches through self.rescue_handlers for appropriate handler.
-
#find_model_instance ⇒ Object
Finds model using provided info in params, prior to any permittance, via where()…first.
-
#find_model_instance! ⇒ Object
Finds model using provided info in params, prior to any permittance, via where()…first! with exception raise if does not exist.
- #handle_or_raise(e) ⇒ Object
-
#include_error_data? ⇒ Boolean
Returns self.return_error_data by default.
-
#index ⇒ Object
The controller’s index (list) method to list resources.
-
#initialize ⇒ Object
In initialize we: * guess model name, if unspecified, from controller name * define instance variables containing model name * define the (model_plural_name)_url method, needed if controllers are not in the same module as the models Note: if controller name is not based on model name and controller is in different module than model, you’ll need to redefine the appropriate method(s) to return urls if needed.
-
#model_class_scoped ⇒ Object
If Rails 3, returns @model_class.scoped.
-
#new ⇒ Object
The controller’s new method (e.g. used for new record in html format).
-
#render_error(e, handling_data) ⇒ Object
Renders error using handling data options (where options are probably hash from self.rescue_handlers that was matched).
- #render_or_respond(read_only_action, success_code = :ok) ⇒ Object
-
#show ⇒ Object
The controller’s show (get) method to return a resource.
- #single_value_response? ⇒ Boolean
-
#update ⇒ Object
The controller’s update (put) method to update a resource.
Instance Method Details
#additional_render_or_respond_success_options ⇒ Object
Returns additional rendering options. By default will massage self.action_to_render_options a little and return that, e.g. if you had used serialize_action to specify an array and each serializer for a specific action, if it is that action, it may return something like: MyFooArraySerializer, each_serializer: MyFooSerializer. If you’d like to do something custom in some situations, but default in others, you may also call default_additional_render_or_respond_success_options from within this method to get the defaults.
412 413 414 |
# File 'lib/restful_json/controller.rb', line 412 def end |
#allowed_params ⇒ Object
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/restful_json/controller.rb', line 317 def allowed_params action_sym = params[:action].to_sym singular = single_value_response? action_specific_params_method = singular ? (@action_to_singular_action_model_params_method[action_sym] ||= "#{action_sym}_#{@model_singular_name}_params".to_sym) : (@action_to_plural_action_model_params_method[action_sym] ||= "#{action_sym}_#{@model_plural_name}_params".to_sym) model_name_params_method = singular ? @model_singular_name_params_sym : @model_plural_name_params_sym if self..include?(action_sym) action_sym, @model_class end if self.actions_that_permit.include?(action_sym) if self.use_permitters return permitted_params_using(self.action_to_permitter[action_sym] || permitter_class) elsif self.allow_action_specific_params_methods && respond_to?(action_specific_params_method) return __send__(action_specific_params_method) elsif self.actions_supporting_params_methods.include?(action_sym) && respond_to?(model_name_params_method) return __send__(model_name_params_method) end end params end |
#apply_includes(value) ⇒ Object
309 310 311 312 313 314 315 |
# File 'lib/restful_json/controller.rb', line 309 def apply_includes(value) this_includes = current_action_includes if this_includes value = value.includes(*this_includes) end value end |
#convert_request_param_value_for_filtering(attr_sym, value) ⇒ Object
234 235 236 |
# File 'lib/restful_json/controller.rb', line 234 def convert_request_param_value_for_filtering(attr_sym, value) value && NILS.include?(value) ? nil : value end |
#create ⇒ Object
The controller’s create (post) method to create a resource.
580 581 582 583 584 585 586 587 588 |
# File 'lib/restful_json/controller.rb', line 580 def create logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug @value = @model_class.new(allowed_params) @value.save instance_variable_set(@model_at_singular_name_sym, @value) render_or_respond(false, :created) rescue self.rescue_class => e handle_or_raise(e) end |
#current_action_includes ⇒ Object
305 306 307 |
# File 'lib/restful_json/controller.rb', line 305 def current_action_includes self.action_to_query_includes[params[:action].to_sym] || self.query_includes end |
#default_additional_render_or_respond_success_options ⇒ Object
416 417 418 419 420 421 422 423 424 425 |
# File 'lib/restful_json/controller.rb', line 416 def result = {} if self.[params[:action].to_sym] custom_action_serializer = self.[params[:action].to_sym][:restful_json_serialization_default] result[(single_value_response? ? :serializer : :each_serializer)] = custom_action_serializer if custom_action_serializer custom_action_array_serializer = self.[params[:action].to_sym][:restful_json_serialization_array] result[:serializer] = custom_action_array_serializer if custom_action_array_serializer && !single_value_response? end result end |
#destroy ⇒ Object
The controller’s destroy (delete) method to destroy a resource.
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 |
# File 'lib/restful_json/controller.rb', line 605 def destroy logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug # don't raise error- DELETE should be idempotent per REST. @value = find_model_instance # allowed_params used primarily for authorization. can't do this to get id param(s) because those are sent via route, not # in wrapped params (if wrapped) allowed_params @value.destroy if @value instance_variable_set(@model_at_singular_name_sym, @value) if !@value.respond_to?(:errors) || @value.errors.empty? || (request.format != 'text/html' && request.content_type != 'text/html') # don't require a destroy view for success, because it isn't implements in Rails by default for json respond_to do |format| format.any { head :ok } end else render_or_respond(false) end rescue self.rescue_class => e handle_or_raise(e) end |
#do_find_model_instance(first_method) ⇒ Object
275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/restful_json/controller.rb', line 275 def do_find_model_instance(first_method) # to_s as safety measure for vulnerabilities similar to CVE-2013-1854. # primary_key array support for composite_primary_keys. if @model_class.primary_key.is_a? Array c = @model_class c.primary_key.each {|pkey|c.where(pkey.to_sym => params[pkey].to_s)} else c = @model_class.where(@model_class.primary_key.to_sym => params[@model_class.primary_key].to_s) end c = apply_includes(c) @value = c.send first_method end |
#edit ⇒ Object
The controller’s edit method (e.g. used for edit record in html format).
567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/restful_json/controller.rb', line 567 def edit logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug @value = find_model_instance! # allowed_params used primarily for authorization. can't do this to get id param(s) because those are sent via route, not # in wrapped params (if wrapped) allowed_params instance_variable_set(@model_at_singular_name_sym, @value) @value rescue self.rescue_class => e handle_or_raise(e) end |
#exception_handling_data(e) ⇒ Object
Searches through self.rescue_handlers for appropriate handler. self.rescue_handlers is an array of hashes where there is key :exception_classes and/or :exception_ancestor_classes along with :i18n_key and :status keys. :exception_classes contains an array of classes to exactly match the exception. :exception_ancestor_classes contains an array of classes that can match an ancestor of the exception. If exception handled, returns hash, hopefully containing keys :i18n_key and :status. Otherwise, returns nil which indicates that this exception should not be handled.
251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/restful_json/controller.rb', line 251 def exception_handling_data(e) self.rescue_handlers.each do |handler| return handler if (handler.key?(:exception_classes) && handler[:exception_classes].include?(e.class)) if handler.key?(:exception_ancestor_classes) handler[:exception_ancestor_classes].each do |ancestor| return handler if e.class.ancestors.include?(ancestor) end elsif !handler.key?(:exception_classes) && !handler.key?(:exception_ancestor_classes) return handler end end nil end |
#find_model_instance ⇒ Object
Finds model using provided info in params, prior to any permittance, via where()…first.
Supports composite_keys.
293 294 295 |
# File 'lib/restful_json/controller.rb', line 293 def find_model_instance do_find_model_instance(:first) end |
#find_model_instance! ⇒ Object
Finds model using provided info in params, prior to any permittance, via where()…first! with exception raise if does not exist.
Supports composite_keys.
301 302 303 |
# File 'lib/restful_json/controller.rb', line 301 def find_model_instance! do_find_model_instance(:first!) end |
#handle_or_raise(e) ⇒ Object
265 266 267 268 269 270 271 272 273 |
# File 'lib/restful_json/controller.rb', line 265 def handle_or_raise(e) raise e if self.rescue_class.nil? handling_data = exception_handling_data(e) raise e unless handling_data # this is something we intended to rescue, so log it logger.error(e) # render error only if we haven't rendered response yet render_error(e, handling_data) unless @performed_render end |
#include_error_data? ⇒ Boolean
Returns self.return_error_data by default. To only return error_data in dev and test, use this: ‘def enable_long_error?; Rails.env.development? || Rails.env.test?; end`
240 241 242 |
# File 'lib/restful_json/controller.rb', line 240 def include_error_data? self.return_error_data end |
#index ⇒ Object
The controller’s index (list) method to list resources.
Note: this method be alias_method’d by query_for, so it is more than just index.
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 |
# File 'lib/restful_json/controller.rb', line 430 def index # could be index or another action if alias_method'd by query_for logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug p_params = allowed_params t = @model_class.arel_table value = model_class_scoped custom_query = self.action_to_query[params[:action].to_sym] if custom_query value = custom_query.call(t, value) end value = apply_includes(value) self.param_to_query.each do |param_name, param_query| if params[param_name] # to_s as safety measure for vulnerabilities similar to CVE-2013-1854 value = param_query.call(t, value, p_params[param_name].to_s) end end self.param_to_through.each do |param_name, through_array| if p_params[param_name] # build query # e.g. SomeModel.all.joins({:assoc_name => {:sub_assoc => {:sub_sub_assoc => :sub_sub_sub_assoc}}).where(sub_sub_sub_assoc_model_table_name: {column_name: value}) last_model_class = @model_class joins = nil # {:assoc_name => {:sub_assoc => {:sub_sub_assoc => :sub_sub_sub_assoc}} through_array.each do |association_or_attribute| if association_or_attribute == through_array.last # 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 # https://groups.google.com/forum/?fromgroups=#!msg/rubyonrails-security/jgJ4cjjS8FE/BGbHRxnDRTIJ value = value.joins(joins).where(last_model_class.table_name.to_sym => {association_or_attribute => p_params[param_name].to_s}) else found_classes = last_model_class.reflections.collect {|association_name, reflection| reflection.class_name.constantize if association_name.to_sym == association_or_attribute}.compact if found_classes.size > 0 last_model_class = found_classes[0] else # bad can_filter_by :through found at runtime raise "Association #{association_or_attribute.inspect} not found on #{last_model_class}." end if joins.nil? joins = association_or_attribute else joins = {association_or_attribute => joins} end end end end end self.param_to_attr_and_arel_predicate.keys.each do |param_name| = param_to_attr_and_arel_predicate[param_name][2] # to_s as safety measure for vulnerabilities similar to CVE-2013-1854 param = p_params[param_name].to_s || [:with_default] if param.present? && param_to_attr_and_arel_predicate[param_name] attr_sym = param_to_attr_and_arel_predicate[param_name][0] predicate_sym = param_to_attr_and_arel_predicate[param_name][1] if predicate_sym == :eq value = value.where(attr_sym => convert_request_param_value_for_filtering(attr_sym, param)) else one_or_more_param = param.split(self.filter_split).collect{|v|convert_request_param_value_for_filtering(attr_sym, v)} value = value.where(t[attr_sym].try(predicate_sym, one_or_more_param)) end end end if p_params[:page] && self.supported_functions.include?(:page) page = p_params[:page].to_i 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? #TODO: to_s is hack to avoid it becoming an Arel::SelectManager for some reason which not sure what to do with value = value.skip((self.number_of_records_in_a_page * (page - 1)).to_s) value = value.take((self.number_of_records_in_a_page).to_s) end if p_params[:skip] && self.supported_functions.include?(:skip) # to_s as safety measure for vulnerabilities similar to CVE-2013-1854 value = value.skip(p_params[:skip].to_s) end if p_params[:take] && self.supported_functions.include?(:take) # to_s as safety measure for vulnerabilities similar to CVE-2013-1854 value = value.take(p_params[:take].to_s) end if p_params[:uniq] && self.supported_functions.include?(:uniq) value = value.uniq end # these must happen at the end and are independent if p_params[:count] && self.supported_functions.include?(:count) value = value.count.to_i elsif p_params[:page_count] && self.supported_functions.include?(:page_count) count_value = value.count.to_i # this executes the query so nothing else can be done in AREL value = (count_value / self.number_of_records_in_a_page) + (count_value % self.number_of_records_in_a_page ? 1 : 0) else #TODO: also declaratively specify order via order=attr1,attr2, etc. like can_filter_by w/queries, subattrs, and direction. self.ordered_by.each do |attr_to_direction| # this looks nasty, but makes no sense to iterate keys if only single of each value = value.order(t[attr_to_direction.keys[0]].send(attr_to_direction.values[0])) end value = value.to_a end @value = value instance_variable_set(@model_at_plural_name_sym, @value) render_or_respond(true) rescue self.rescue_class => e handle_or_raise(e) end |
#initialize ⇒ Object
In initialize we:
-
guess model name, if unspecified, from controller name
-
define instance variables containing model name
-
define the (model_plural_name)_url method, needed if controllers are not in the same module as the models
Note: if controller name is not based on model name and controller is in different module than model, you’ll need to redefine the appropriate method(s) to return urls if needed.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/restful_json/controller.rb', line 198 def initialize super # if not set, use controller classname qualified_controller_name = self.class.name.chomp('Controller') @model_class = self.model_class || qualified_controller_name.split('::').last.singularize.constantize raise "#{self.class.name} failed to initialize. self.model_class was nil in #{self} which shouldn't happen!" if @model_class.nil? 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) @model_singular_name = self.model_singular_name || self.model_class.name.underscore @model_plural_name = self.model_plural_name || @model_singular_name.pluralize @model_at_plural_name_sym = "@#{@model_plural_name}".to_sym @model_at_singular_name_sym = "@#{@model_singular_name}".to_sym # default methods for strong parameters @model_plural_name_params_sym = "#{@model_plural_name}_params".to_sym @model_singular_name_params_sym = "#{@model_singular_name}_params".to_sym @action_to_singular_action_model_params_method = {} @action_to_plural_action_model_params_method = {} underscored_modules_and_underscored_plural_model_name = qualified_controller_name.gsub('::','_').underscore # 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. # If the model name is different than the controller name, you will need to define methods to return the right urls. 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 singularized_underscored_modules_and_underscored_plural_model_name = underscored_modules_and_underscored_plural_model_name 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 end |
#model_class_scoped ⇒ Object
If Rails 3, returns @model_class.scoped. If not, returns @model_class.all. This helps avoid a deprecation warning.
230 231 232 |
# File 'lib/restful_json/controller.rb', line 230 def model_class_scoped Rails::VERSION::MAJOR == 3 ? @model_class.scoped : @model_class.all end |
#new ⇒ Object
The controller’s new method (e.g. used for new record in html format).
554 555 556 557 558 559 560 561 562 563 564 |
# File 'lib/restful_json/controller.rb', line 554 def new logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug # allowed_params used primarily for authorization. can't do this to get id param(s) because those are sent via route, not # in wrapped params (if wrapped) allowed_params @value = @model_class.new instance_variable_set(@model_at_singular_name_sym, @value) render_or_respond(true) rescue self.rescue_class => e handle_or_raise(e) end |
#render_error(e, handling_data) ⇒ Object
Renders error using handling data options (where options are probably hash from self.rescue_handlers that was matched).
If include_error_data? is true, it returns something like the following (with the appropriate HTTP status code via setting appropriate status in respond_do: “not_found”,
"error": "Internationalized error message or e.message",
"error_data": {"type": "ActiveRecord::RecordNotFound", "message": "Couldn't find Bar with id=23423423", "trace": ["backtrace line 1", ...]
}
If include_error_data? is false, it returns something like: {“status”: “not_found”, “error”, “Couldn’t find Bar with id=23423423”}
It handles any format in theory that is supported by respond_to and has a ‘to_(some format)` method.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/restful_json/controller.rb', line 352 def render_error(e, handling_data) i18n_key = handling_data[:i18n_key] msg = t(i18n_key, default: e.) status = handling_data[:status] || :internal_server_error if include_error_data? respond_to do |format| format.html { render notice: msg } format.any { render request.format.to_sym => {status: status, error: msg, error_data: {type: e.class.name, message: e., trace: Rails.backtrace_cleaner.clean(e.backtrace)}}, status: status } end else respond_to do |format| format.html { render notice: msg } format.any { render request.format.to_sym => {status: status, error: msg}, status: status } end end # return exception so we know it was handled e end |
#render_or_respond(read_only_action, success_code = :ok) ⇒ Object
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/restful_json/controller.rb', line 371 def render_or_respond(read_only_action, success_code = :ok) if self.render_enabled # 404/not found is just for update (not destroy, because idempotent destroy = no 404) if success_code == :not_found respond_to do |format| format.html { render file: "#{Rails.root}/public/404.html", status: :not_found } format.any { head :not_found } end elsif !@value.nil? && ((read_only_action && RestfulJson.return_resource) || RestfulJson.avoid_respond_with) respond_with(@value) do |format| format.json do if !@value.respond_to?(:errors) || @value.errors.empty? result = {json: @value, status: success_code} result.merge!() else result = {json: {errors: @value.errors}, status: :unprocessable_entity} end render result end end else if !@value.respond_to?(:errors) || @value.errors.empty? respond_with @value, else respond_with @value end end else @value end end |
#show ⇒ Object
The controller’s show (get) method to return a resource.
541 542 543 544 545 546 547 548 549 550 551 |
# File 'lib/restful_json/controller.rb', line 541 def show logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug @value = find_model_instance! # allowed_params used primarily for authorization. can't do this to get id param(s) because those are sent via route, not # in wrapped params (if wrapped) allowed_params instance_variable_set(@model_at_singular_name_sym, @value) render_or_respond(true, @value.nil? ? :not_found : :ok) rescue self.rescue_class => e handle_or_raise(e) end |
#single_value_response? ⇒ Boolean
403 404 405 |
# File 'lib/restful_json/controller.rb', line 403 def single_value_response? SINGLE_VALUE_ACTIONS.include?(params[:action].to_sym) end |
#update ⇒ Object
The controller’s update (put) method to update a resource.
591 592 593 594 595 596 597 598 599 600 601 602 |
# File 'lib/restful_json/controller.rb', line 591 def update logger.debug "#{params[:action]} called in #{self.class}: model=#{@model_class}, request.format=#{request.format}, request.content_type=#{request.content_type}, params=#{params.inspect}" if self.debug @value = find_model_instance! # allowed_params used primarily for authorization. can't do this to get id param(s) because those are sent via route, not # in wrapped params (if wrapped) p_params = allowed_params @value.update_attributes(p_params) unless @value.nil? instance_variable_set(@model_at_singular_name_sym, @value) render_or_respond(true, @value.nil? ? :not_found : :ok) rescue self.rescue_class => e handle_or_raise(e) end |