lib/survey_gizmo/resource.rb in survey-gizmo-ruby-0.7.1 vs lib/survey_gizmo/resource.rb in survey-gizmo-ruby-0.9.6

- old
+ new

@@ -1,124 +1,137 @@ require "set" +require "addressable/uri" module SurveyGizmo module Resource extend ActiveSupport::Concern - + included do include Virtus instance_variable_set('@paths', {}) instance_variable_set('@collections', {}) SurveyGizmo::Resource.descendants << self end - + # @return [Set] Every class that includes SurveyGizmo::Resource def self.descendants @descendants ||= Set.new end - + # 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 + # @return [String] + def convert_filters_into_query_string(filters = nil) + "" unless filters && filters.size > 0 + uri = Addressable::URI.new + uri.query_values = filters + "?#{uri.query}" + end + # Get a list of resources # @param [Hash] conditions + # @param [Hash] filters # @return [SurveyGizmo::Collection, Array] - def all(conditions = {}) - response = Response.new SurveyGizmo.get(handle_route(:create, conditions)) + def all(conditions = {}, filters = nil) + response = Response.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 else [] end end - + # Get the first resource # @param [Hash] conditions + # @param [Hash] filters # @return [Object, nil] - def first(conditions) - response = Response.new SurveyGizmo.get(handle_route(:get, conditions)) + 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 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 end - + # 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? end - + # Define the path where a resource is located - # @param [String] path + # @param [String] path # the path in Survey Gizmo for the resource - # @param [Hash] options + # @param [Hash] options # @option options [Array] :via # 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) nil end - + # @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] + # @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) 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) } - path.gsub(/:(\w+)/) do |m| + path.gsub(/:(\w+)/) do |m| options[$1.to_sym].tap{ |result| raise(SurveyGizmo::URLError, "Missing parameters in request: `#{m}`") unless result } end end end - + # Updates attributes and saves this Resource instance # # @param [Hash] attributes # attributes to be updated # @@ -126,11 +139,11 @@ # 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 @@ -140,40 +153,40 @@ handle_response SurveyGizmo.post(handle_route(:update), :query => self.attributes_without_blanks) do _response.ok? ? saved! : false end 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? + if _response.ok? self.attributes = _response.data clean! else false end - end + 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 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! @@ -182,97 +195,148 @@ # 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 - + # Any errors returned by Survey Gizmo # @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.attributes.map do |attrib| value = attrib.get!(self).inspect - "#{attrib.instance_variable_name}=#{value}" + "#{attrib.instance_variable_name}=#{value}" if attrib.respond_to?(:instance_variable_name) end "#<#{self.class.name}:#{self.object_id} #{attrs.join(' ')}>" end - + # This class normalizes the response returned by Survey Gizmo class Response def ok? @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" + end + end + def initialize(response) @response = response.parsed_response + return unless @response['data'].class == Hash + @_data = @response['data'] + + # Handle really crappy [] notation in SG API, so far just in SurveyResponse + @_data.keys.grep(/^\[/).each do |key| + next unless @_data[key].length > 0 + + parent = find_attribute_parent(key) + @_data[parent] = {} unless @_data[parent] + + case key.downcase + when /(url|variable.*standard)/ + @_data[parent][cleanup_attribute_name(key).to_sym] = @_data[key] + when /variable.*shown/ + @_data[parent][cleanup_attribute_name(key).to_i] = @_data[key].include?("1") + when /variable/ + @_data[parent][cleanup_attribute_name(key).to_i] = @_data[key].to_i + when /question/ + @_data[parent][key] = @_data[key] + end + + @_data.delete(key) + end end end - protected + protected + def attributes_without_blanks 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 + end end end - + end end \ No newline at end of file