module Serviceable::ClassMethods
Public Instance Methods
acts_as_service(object,defaults={})
click to toggle source
Serviceable
Usage
Controller: class PostsController
acts_as_service :post
end
# File lib/serviceable.rb, line 25 def acts_as_service(object,defaults={}) before_action :assign_new_instance, only: :create before_action :did_assign_new_instance, only: :create before_action :assign_existing_instance, only: [ :show, :update, :destroy ] before_action :did_assign_existing_instance, only: [ :show, :update ] before_action :assign_collection, only: [ :index, :count ] before_action :did_assign_collection, only: [ :index, :count ] define_method("index") do respond_to do |format| format.json { render json: @collection.to_json(merge_options(defaults[:index])) } format.xml { render xml: @collection.to_xml(merge_options(defaults[:index])) } end end define_method("count") do respond_to do |format| format.json { render json: @collection.count } format.xml { render xml: @collection.count } end end define_method("create") do respond_to do |format| if @instance.save format.json { render json: @instance, status: :created } format.xml { render xml: @instance, status: :created } else format.json { render json: { errors: @instance.errors.full_messages }, status: :unprocessable_entity } format.xml { render xml: { errors: @instance.errors.full_messages }, status: :unprocessable_entity } end end end define_method("show") do respond_to do |format| format.json { render json: @instance.to_json(merge_options(defaults[:show])) } format.xml { render xml: @instance.to_xml(merge_options(defaults[:show])) } end end define_method("update") do respond_to do |format| if @instance.update_attributes(params[object]) format.json { head :ok } format.xml { head :ok } else format.json { render json: { errors: @instance.errors.full_messages }, status: :unprocessable_entity } format.xml { render xml: { errors: @instance.errors.full_messages }, status: :unprocessable_entity } end end end define_method("destroy") do @instance.destroy respond_to do |format| format.json { head :no_content } format.xml { head :no_content } end end define_method("describe") do details = { allowed_includes: force_array(defaults[:allowed_includes]), allowed_methods: force_array(defaults[:allowed_methods]) } respond_to do |format| format.json { render json: details.to_json, status: :ok } format.xml { render xml: details.to_xml, status: :ok } end end # query string params can be given in the following formats: # only=field1,field2 # except=field1,field2 # include=assoc1 # methods=my_helper # # if an included association is present, only and except params can be nested # include[user][except]=encrypted_password # include[user][only][]=first_name&include[user][only][]=last_name # include[user][only]=first_name,last_name # # NOTE: includes and methods are not supported for nested associations # # options specified by the developer are considered mandatory and can not be # overridden by the client # # client may only use includes and methods that are explicitly enabled by # the developer define_method("merge_options") do |options={}| merged_options = {} for key in [:only, :except] opts = {key => params[key]} if params[key] merged_options = merged_options.merge(opts) if opts end requested_includes = hash_for(params[:includes]) allowed_includes = hash_for(defaults[:allowed_includes]) requested_includes = deep_sym(requested_includes) allowed_includes = deep_sym(allowed_includes) whitelisted_includes = {} requested_includes.keys.each do |k| if allowed_includes.keys.include?(k) values = requested_includes[k] opts = {} opts[:only] = values[:only] if values[:only] opts[:except] = values[:except] if values[:except] whitelisted_includes[k] = opts end end if options && options[:include] if options[:include].kind_of?(Hash) mandatory_includes = options[:include] elsif options[:include].kind_of?(Array) mandatory_includes = Hash[options[:include].map {|e| [e,{}]}] else mandatory_includes = {options[:include] => {}} end whitelisted_includes = whitelisted_includes.merge(mandatory_includes) end merged_options = merged_options.merge({include: whitelisted_includes}) if whitelisted_includes.keys.any? requested_methods = array_for(params[:methods]) allowed_methods = array_for(defaults[:allowed_methods]) requested_methods = requested_methods.map(&:to_s).map(&:to_sym) allowed_methods = allowed_methods.map(&:to_s).map(&:to_sym) whitelisted_methods = requested_methods & allowed_methods if options && options[:methods] mandatory_methods = array_for(options[:methods]) whitelisted_methods = whitelisted_methods + mandatory_methods end merged_options = merged_options.merge({methods: whitelisted_methods}) if whitelisted_methods.any? merged_options = deep_split(merged_options.compact) return merged_options end define_method("assign_existing_instance") do @instance = object.to_s.camelize.constantize.all if params[:include].kind_of?(Hash) @instance = @instance.includes(params[:include].keys) end if params[:include].kind_of?(String) @instance = @instance.includes(params[:include].split(",").map(&:to_sym)) end @instance = @instance.find(params[:id]) end define_method("did_assign_existing_instance") do # do nothing end define_method("assign_new_instance") do @instance = object.to_s.camelize.constantize.new(object_params) end define_method("did_assign_new_instance") do # do nothing end define_method("object_params") do params.require(object).permit! end # query string params can be used to filter collections # # filters apply on associated collections using the following conventions: # where[user][category]=Expert # where[user][created_at][gt]=20130807T12:34:56.789Z # # filters can be constructed with AND and OR behavior # where[tags][id][in]=123,234,345 (OR) # where[tags][id]=123&where[tags][id]=234 (AND) define_method("assign_collection") do @collection = object.to_s.camelize.constantize.all if params[:include].kind_of?(Hash) for assoc in params[:include].keys @collection = @collection.includes(assoc.to_sym) end end if params[:include].kind_of?(String) @collection = @collection.includes(params[:include].split(",").map(&:to_sym)) end for assoc in (params[:where].keys rescue []) attrs = params[:where][assoc] if attrs.kind_of?(Hash) for target_column in attrs.keys if attrs[target_column].kind_of?(String) if is_boolean_column?(target_column) value = true if ['t','true','1','y','yes'].include?(attrs[target_column].to_s) value = false if ['f','false','0','n','no'].include?(attrs[target_column].to_s) else value = attrs[target_column] end @collection = @collection.where(assoc => { target_column => value }) elsif attrs[target_column].kind_of?(Hash) for op in attrs[target_column].keys.map(&:to_sym) value = is_time_column?(target_column) ? Time.parse(attrs[target_column][op]) : attrs[target_column][op] unless assoc.to_sym==object.to_s.pluralize.to_sym @collection = @collection.includes(assoc) end if op==:gt @collection = @collection.where("#{assoc}.#{target_column} > ?",value) elsif op==:lt @collection = @collection.where("#{assoc}.#{target_column} < ?",value) elsif op==:in @collection = @collection.where("#{assoc}.#{target_column} IN (?)",value.split(',')) end end end end else @collection = @collection.includes(assoc).where(assoc => attrs) end end end define_method("did_assign_collection") do # do nothing end define_method("array_for") do |obj| if obj.kind_of?(Hash) arr = obj.keys elsif obj.kind_of?(Array) arr = obj elsif obj.kind_of?(String) arr = obj.split(',') else arr = Array(obj) end arr.compact.uniq rescue [] end # designed to traverse an entire hash, replacing delimited strings with arrays of symbols define_method("deep_split") do |hash={},pivot=','| Hash[hash.reject {|k,v| k.nil? || v.nil?}.map {|k,v| [k.to_sym,v.kind_of?(String) ? v.split(pivot).compact.map(&:to_sym) : (v.kind_of?(Hash) ? deep_split(v,pivot) : v)]}] end define_method("deep_sym") do |hash={}| Hash[hash.reject {|k,v| k.nil? || v.nil?}.map {|k,v| [k.to_sym,v.kind_of?(String) ? v.to_sym : (v.kind_of?(Hash) ? deep_sym(v) : (v.kind_of?(Array) ? v.compact.map(&:to_sym) : v))]}] end define_method("force_array") do |obj| obj.kind_of?(Array) ? obj : (obj.kind_of?(Hash) ? obj.keys : (obj==nil ? [] : [obj])) end define_method("hash_for") do |obj| if obj.kind_of?(Hash) hash = obj elsif obj.kind_of?(Array) hash = Hash[obj.map {|e| [e,{}]}] elsif obj.kind_of?(String) hash = Hash[obj.split(',').map {|e| [e,{}]}] else hash = {} end hash end define_method("required_fields") do object.to_s.capitalize.constantize.accessible_attributes.select {|e| is_required_column?(e)} end define_method("is_time_column?") do |column| object.to_s.capitalize.constantize.columns.select {|e| e.name==column.to_s}.first.type == :timestamp rescue false end define_method("is_boolean_column?") do |column| object.to_s.capitalize.constantize.columns.select {|e| e.name==column.to_s}.first.type == :boolean rescue false end define_method("is_required_column?") do |column| object.to_s.capitalize.constantize.validators_on(column).map(&:class).include?(ActiveModel::Validations::PresenceValidator) end end