lib/apidiesel/action.rb in apidiesel-0.12 vs lib/apidiesel/action.rb in apidiesel-0.13

- old
+ new

@@ -7,19 +7,24 @@ # accessors for class instance variables # (class-level variables, not shared with subclasses) class << self include Handlers - attr_reader :url_args + attr_reader :url_value, :url_args - # Hash for storing validation closures. These closures are called with the request + # Array for storing parameter validation closures. These closures are called with the request # parameters before the request is made and have the opportunity to check and modify them. def parameter_validations @parameter_validations ||= [] end - # Hash for storing filter closures. These closures are called with the received data + # Array for storing action argument names which are not to be submitted as parameters + def parameters_to_filter + @parameters_to_filter ||= [] + end + + # Array for storing filter closures. These closures are called with the received data # after a request is made and have the opportunity to modify or check it before the # data is returned def response_filters @response_filters ||= [] end @@ -45,25 +50,82 @@ else @endpoint end end - # Combined getter/setter for this actions URL + # Defines this Actions URL, or modifies the base URL set on `Api` # - # Falls back to the Api setting if blank. + # Given keyword arguments such as `path:` will be applied to + # the `URI` object supplied to `Api.url`. # - # @param [String] value - def url(value = nil, **args) - return @url unless value || args.any? - - if value && value.is_a?(Proc) - @url = value - elsif value - @url = URI.parse(value) - else - @url_args = args + # Accepts a `Proc`, which will be called at request time with + # the URL constructed so far and the current `Request` object. + # + # A string value and all keyword arguments can contain + # placeholders for all arguments supplied to the action in + # Rubys standard `String.%` syntax. + # + # @example + # class Api < Apidiesel::Api + # url 'https://foo.example' + # + # register_actions + # end + # + # module Actions + # # modify the base URL set on `Api` + # class ActionA < Apidiesel::Action + # url path: '/action_a' + # end + # + # # replace the base URL set on `Api` + # class ActionB < Apidiesel::Action + # url 'https://subdomain.foo.example' + # end + # + # # modify the base URL set on `Api` with a + # # 'username' argument placeholder + # class ActionC < Apidiesel::Action + # url path: '/action_c/%{username}' + # + # expects do + # string :username, submit: false + # end + # end + # + # # dynamically determine the URL with a + # # `Proc` object + # class ActionD < Apidiesel::Action + # url ->(url, request) { + # url.path = '/' + request.action_arguments[:username] + # .downcase + # url + # } + # + # expects do + # string :username, submit: false + # end + # end + # end + # + # @overload url(value) + # @param [String, URI] value a complete URL string or `URI` + # + # @overload url(**kargs) + # @option **kargs [String] any method name valid on Rubys `URI::Generic` + # + # @overload url(value) + # @param [Proc] value a callback that returns a URL string at request time. + # Receives the URL contructed so far and the current + # `Request` instance. + def url(value = nil, **kargs) + if value && kargs.any? + raise ArgumentError, "you cannot supply both argument and keyword args" end + + @url_value = value + @url_args = kargs end # Combined getter/setter for the HTTP method used # # Falls back to the Api setting if blank. @@ -126,57 +188,37 @@ def endpoint self.class.endpoint end - def base_url - if self.class.url.nil? || self.class.url.is_a?(Proc) - @api.class.url.dup - else - self.class.url.dup - end - end - - def url - if self.class.url.is_a?(Proc) - url = self.class.url - - elsif self.class.url_args - url = base_url - - self.class.url_args.each do |key, value| - url.send("#{key}=", value) - end - end - - url - end - def http_method - self.class.http_method || @api.class.http_method + self.class.http_method || @api.class.http_method || :get end # Performs the action-specific input validations on `*args` according to the actions # `expects` block, executes the API request and prepares the data according to the # actions `responds_with` block. # - # @param [Hash] *args see specific, non-abstract `Apidiesel::Action` + # @option **args see specific, non-abstract `Apidiesel::Action` # @return [Apidiesel::Request] - def build_request(*args) - args = args && args.first.is_a?(Hash) ? args.first : {} - + def build_request(**args) params = {} self.class.parameter_validations.each do |validation| validation.call(args, params) end if self.class.parameter_formatter params = self.class.parameter_formatter.call(params) + else + params.except!(*self.class.parameters_to_filter) end - Apidiesel::Request.new action: self, parameters: params + request = Apidiesel::Request.new(action: self, action_arguments: args, parameters: params) + request.url = build_url(args, request) + + request end def process_response(response_data) processed_result = {} @@ -205,9 +247,37 @@ processed_result end protected + + # @return [URI] + def build_url(action_arguments, request) + url = case self.class.url_value + when String + URI( self.class.url_value % action_arguments ) + when URI + self.class.url_value + when Proc + self.class.url_value.call(base_url, request) + when nil + base_url + end + + url_args = self.class.url_args.transform_values do |value| + value % action_arguments + end + + url_args.each do |name, value| + url.send("#{name}=", value) + end + + url + end + + def base_url + @api.class.url.nil? ? URI('http://') : @api.class.url.dup + end # @return [Hash] Apidiesel configuration options def config Apidiesel::CONFIG[environment] end