lib/survey_gizmo/resource.rb in survey-gizmo-ruby-1.0.5 vs lib/survey_gizmo/resource.rb in survey-gizmo-ruby-2.0.0
- old
+ new
@@ -1,16 +1,15 @@
-require "set"
-require "addressable/uri"
+require 'set'
+require 'addressable/uri'
module SurveyGizmo
module Resource
extend ActiveSupport::Concern
included do
include Virtus.model
instance_variable_set('@paths', {})
- instance_variable_set('@collections', {})
SurveyGizmo::Resource.descendants << self
end
# @return [Set] Every class that includes SurveyGizmo::Resource
def self.descendants
@@ -20,28 +19,62 @@
# These are methods that every API resource has to access resources
# in Survey Gizmo
module ClassMethods
# Convert a [Hash] of filters into a query string
- # @param [Hash] filters
+ # @param [Hash] filters - simple pagination or other options at the top level, and surveygizmo "filters" at the :filters key
# @return [String]
+ #
+ # example input: {page: 2, filters: [{:field=>"istestdata", :operator=>"<>", :value=>1}]}
+ # The top level keys (e.g. page, resultsperpage) get simply encoded in the url, while the contents of the array of hashes
+ # passed at filters[:filters] gets turned into the format surveygizmo expects for its internal filtering, for example:
+ #
+ # filter[field][0]=istestdata&filter[operator][0]=<>&filter[value][0]=1
def convert_filters_into_query_string(filters = nil)
- "" unless filters && filters.size > 0
- uri = Addressable::URI.new
- uri.query_values = filters
- "?#{uri.query}"
+ if filters && filters.size > 0
+ output_filters = filters[:filters] || []
+ filter_hash = {}
+ output_filters.each_with_index do |filter,i|
+ filter_hash.merge!({
+ "filter[field][#{i}]".to_sym => "#{filter[:field]}",
+ "filter[operator][#{i}]".to_sym => "#{filter[:operator]}",
+ "filter[value][#{i}]".to_sym => "#{filter[:value]}",
+ })
+ end
+ simple_filters = filters.reject {|k,v| k == :filters}
+ filter_hash.merge!(simple_filters)
+
+ uri = Addressable::URI.new
+ uri.query_values = filter_hash
+ "?#{uri.query}"
+ else
+ ''
+ end
end
# Get a list of resources
# @param [Hash] conditions
# @param [Hash] filters
- # @return [SurveyGizmo::Collection, Array]
+ # @return [Array] of objects of this class
def all(conditions = {}, filters = nil)
- response = Response.new SurveyGizmo.get(handle_route(:create, conditions) + convert_filters_into_query_string(filters))
+ response = RestResponse.new(SurveyGizmo.get(handle_route(:create, conditions) + convert_filters_into_query_string(filters)))
if response.ok?
- _collection = SurveyGizmo::Collection.new(self, nil, response.data)
- _collection.send(:options=, {:target => self, :parent => self})
+ _collection = response.data.map {|datum| datum.is_a?(Hash) ? self.new(datum) : datum}
+
+ # Add in the properties from the conditions hash because many of the important ones (like survey_id) are
+ # not often part of the SurveyGizmo returned data
+ conditions.keys.each do |k|
+ if conditions[k] && instance_methods.include?(k)
+ _collection.each { |c| c[k] ||= conditions[k] }
+ end
+ end
+
+ # Sub questions are not pulled by default so we have to retrieve them
+ if self == SurveyGizmo::API::Question
+ _collection += _collection.map {|question| question.sub_questions}.flatten
+ end
+
_collection
else
[]
end
end
@@ -49,21 +82,23 @@
# Get the first resource
# @param [Hash] conditions
# @param [Hash] filters
# @return [Object, nil]
def first(conditions = {}, filters = nil)
- response = Response.new SurveyGizmo.get(handle_route(:get, conditions) + convert_filters_into_query_string(filters))
- response.ok? ? load(conditions.merge(response.data)) : nil
+ response = RestResponse.new(SurveyGizmo.get(handle_route(:get, conditions) + convert_filters_into_query_string(filters)))
+ # Add in the properties from the conditions hash because many of the important ones (like survey_id) are
+ # not often part of the SurveyGizmo's returned data
+ response.ok? ? new(conditions.merge(response.data)) : nil
end
# Create a new resource
# @param [Hash] attributes
# @return [Resource]
# The newly created Resource instance
def create(attributes = {})
resource = new(attributes)
- resource.__send__(:_create)
+ resource.create_record_in_surveygizmo
resource
end
# Copy a resource
# @param [Integer] id
@@ -79,12 +114,11 @@
# Deleted the Resource from Survey Gizmo
# @param [Hash] conditions
# @return [Boolean]
def destroy(conditions)
- response = Response.new SurveyGizmo.delete(handle_route(:delete, conditions))
- response.ok?
+ RestResponse.new(SurveyGizmo.delete(handle_route(:delete, conditions))).ok?
end
# Define the path where a resource is located
# @param [String] path
# the path in Survey Gizmo for the resource
@@ -93,133 +127,63 @@
# which is `:get`, `:create`, `:update`, `:delete`, or `:any`
# @scope class
def route(path, options)
methods = options[:via]
methods = [:get, :create, :update, :delete] if methods == :any
- methods.is_a?(Array) ? methods.each{|m| @paths[m] = path } : (@paths[methods] = path)
+ methods.is_a?(Array) ? methods.each { |m| @paths[m] = path } : (@paths[methods] = path)
nil
end
+ # This method replaces the :page_id, :survey_id, etc strings defined in each model's URI routes with the
+ # values being passed in interpolation hash with the same keys.
# @api private
- def load(attributes = {})
- resource = new(attributes)
- resource.__send__(:clean!)
- resource
- end
-
- # Defines a new collection. These are child objects of the resource.
- # @macro [new] collection
- # @param [Symbol] resource_name the name of the collection, pluralized
- # @param [Class] model and optional class name if the class name does not match the resource_name
- # @return [Collection]
- # the $1 collection
- # @scope instance
- def collection(resource_name, model = nil)
- @collections[resource_name] = {:parent => self, :target => (model ? model : resource_name)} # workaround for weird bug with passing a class to Collection
- class_eval(<<-EOS)
- def #{resource_name}
- @#{resource_name} ||= []
- end
-
- def #{resource_name}=(array)
- @#{resource_name} = SurveyGizmo::Collection.new(#{self}, :#{resource_name}, array)
- end
- EOS
- end
-
- # @api private
- def collections
- @collections.dup.freeze
- end
-
- # @api private
- def handle_route(key, *interp)
+ def handle_route(key, interpolation_hash)
path = @paths[key]
raise "No routes defined for `#{key}` in #{self.name}" unless path
- options = interp.last.is_a?(Hash) ? interp.pop : path.scan(/:(\w+)/).inject({}){|hash, k| hash.merge(k.to_sym => interp.shift) }
+ raise "User/password hash not setup!" if SurveyGizmo.default_params.empty?
+
path.gsub(/:(\w+)/) do |m|
- options[$1.to_sym].tap{ |result| raise(SurveyGizmo::URLError, "Missing parameters in request: `#{m}`") unless result }
+ raise(SurveyGizmo::URLError, "Missing RESTful parameters in request: `#{m}`") unless interpolation_hash[$1.to_sym]
+ interpolation_hash[$1.to_sym]
end
end
end
- # Updates attributes and saves this Resource instance
- #
- # @param [Hash] attributes
- # attributes to be updated
- #
- # @return [Boolean]
- # true if resource is saved
- def update(attributes = {})
- self.attributes = attributes
- self.save
- end
-
# Save the instance to Survey Gizmo
- #
- # @return [Boolean]
- # true if Resource instance is saved
def save
- if new?
- _create
+ if id #Then it's an update
+ handle_response(SurveyGizmo.post(handle_route(:update), query: self.attributes_without_blanks))
+ @latest_response.ok?
else
- handle_response SurveyGizmo.post(handle_route(:update), :query => self.attributes_without_blanks) do
- _response.ok? ? saved! : false
- end
+ create_record_in_surveygizmo
end
end
# fetch resource from SurveyGizmo and reload the attributes
# @return [self, false]
# Returns the object, if saved. Otherwise returns false.
def reload
- handle_response SurveyGizmo.get(handle_route(:get)) do
- if _response.ok?
- self.attributes = _response.data
- clean!
- else
- false
- end
+ handle_response(SurveyGizmo.get(handle_route(:get)))
+ if @latest_response.ok?
+ self.attributes = @latest_response['data']
+ self
+ else
+ false
end
end
# Deleted the Resource from Survey Gizmo
# @return [Boolean]
def destroy
- return false if new? || destroyed?
- handle_response SurveyGizmo.delete(handle_route(:delete)) do
- _response.ok? ? destroyed! : false
+ if id
+ handle_response(SurveyGizmo.delete(handle_route(:delete)))
+ @latest_response.ok?
+ else
+ false
end
end
- # The state of the current Resource
- # @api private
- def new?
- @_state.nil?
- end
-
- # @todo This seemed like a good way to prevent accidently trying to perform an action
- # on a record at a point when it would fail. Not sure if it's really necessary though.
- [:clean, # stored and not dirty
- :saved, # stored and not modified
- :destroyed, # duh!
- :zombie # needs to be stored
- ].each do |state|
- # Change the method state to $1
- define_method("#{state}!") do
- @_state = state
- true
- end
-
- # Inquire about the method state if $1
- define_method("#{state}?") do
- @_state == state
- end
-
- private "#{state}!"
- end
-
# Sets the hash that will be used to interpolate values in routes. It needs to be defined per model.
# @return [Hash] a hash of the values needed in routing
def to_param_options
raise "Define #to_param_options in #{self.class.name}"
end
@@ -228,142 +192,78 @@
# @return [Array]
def errors
@errors ||= []
end
- # @return [Hash] The raw JSON returned by Survey Gizmo
- def raw_response
- _response.response if _response
- end
-
# @visibility private
def inspect
- attrs = self.class.attribute_set.map do |attrib|
- value = attrib.get!(self).inspect
-
- "@#{attrib.name}=#{value}" if attrib.respond_to?(:name)
+ if ENV['GIZMO_DEBUG']
+ ap "CLASS: #{self.class}"
end
- "#<#{self.class.name}:#{self.object_id} #{attrs.join(' ')}>"
- end
+ attribute_strings = self.class.attribute_set.map do |attrib|
+ if ENV['GIZMO_DEBUG']
+ ap attrib
+ ap attrib.name
+ ap self.send(attrib.name)
+ ap self.send(attrib.name).class
+ end
- # This class normalizes the response returned by Survey Gizmo
- class Response
- def ok?
- @response && @response['result_ok']
- end
-
- # The parsed JSON data of the response
- def data
- @_data ||= (@response['data'] || {})
- end
-
- # The error message if there is one
- def message
- @_message ||= @response['message']
- end
-
- attr_reader :response
-
- private
- def cleanup_attribute_name(attr)
- attr.downcase.gsub(/[^[:alnum:]]+/,'_').gsub(/(url|variable|standard|shown)/,'').gsub(/_+/,'_').gsub(/^_/,'').gsub(/_$/,'')
- end
-
- def find_attribute_parent(attr)
- case attr.downcase
- when /url/
- "url"
- when /variable.*standard/
- "meta"
- when /variable.*shown/
- "shown"
- when /variable/
- "variable"
- when /question/
- "answers"
+ if self.send(attrib.name).class == Hash
+ value = self.send(attrib.name).inspect
+ else
+ value = self.send(attrib.name).to_s
end
- end
- def initialize(response)
- @response = response.parsed_response
- return if @response.nil? or not ok?
- @_data = @response['data']
+ " \"#{attrib.name}\" => \"#{value}\"\n" unless value.strip.blank?
+ end.compact
- # Handle really crappy [] notation in SG API, so far just in SurveyResponse
- items = (@_data.is_a?(Array) ? @_data : [@_data]).compact
- items.each do |data_item|
- data_item.keys.grep(/^\[/).each do |key|
- next if data_item[key].nil? || data_item[key].length == 0
+ "#<#{self.class.name}:#{self.object_id}>\n#{attribute_strings.join()}"
+ end
- parent = find_attribute_parent(key)
- data_item[parent] = {} unless data_item[parent]
-
- case key.downcase
- when /(url|variable.*standard)/
- data_item[parent][cleanup_attribute_name(key).to_sym] = data_item[key]
- when /variable.*shown/
- data_item[parent][cleanup_attribute_name(key).to_i] = data_item[key].include?("1")
- when /variable/
- data_item[parent][cleanup_attribute_name(key).to_i] = data_item[key].to_i
- when /question/
- data_item[parent][key] = data_item[key]
- end
-
- data_item.delete(key)
- end
- end unless items.blank?
+ # Returns itself if successfully saved, but with attributes added by SurveyGizmo
+ def create_record_in_surveygizmo(attributes = {})
+ http = RestResponse.new(SurveyGizmo.put(handle_route(:create), query: self.attributes_without_blanks))
+ handle_response(http)
+ if http.ok?
+ self.attributes = http.data
+ self
+ else
+ false
end
end
protected
def attributes_without_blanks
- self.attributes.reject{|k,v| v.blank? }
+ self.attributes.reject { |k,v| v.blank? }
end
private
- # The response object from SurveyGizmo. Useful for viewing the raw data returned
- attr_reader :_response
-
- def set_response(http)
- @_response = Response.new(http)
- end
-
def handle_route(key)
self.class.handle_route(key, to_param_options)
end
- def handle_response(resp, &block)
- set_response(resp)
- (self.errors << _response.message) unless _response.ok?
- self.errors.clear if !self.errors.empty? && _response.ok?
- instance_eval(&block)
- end
-
- def _create(attributes = {})
- http = SurveyGizmo.put(handle_route(:create), :query => self.attributes_without_blanks)
- handle_response http do
- if _response.ok?
- self.attributes = _response.data
- saved!
- else
- false
- end
+ def handle_response(rest_response, &block)
+ @latest_response = rest_response
+ if @latest_response.ok?
+ self.errors.clear
+ true
+ else
+ errors << @latest_response.message
+ false
end
end
def _copy(attributes = {})
- http = SurveyGizmo.post(handle_route(:update), :query => self.attributes_without_blanks)
- handle_response http do
- if _response.ok?
- self.attributes = _response.data
- saved!
+ http = RestResponse.new(SurveyGizmo.post(handle_route(:update), query: self.attributes_without_blanks))
+ handle_response(http) do
+ if http.ok?
+ self.attributes = http.data
else
false
end
end
end
-
end
end