class Scorpio::ResourceBase
Attributes
attributes[R]
options[R]
Public Class Methods
all_schema_properties()
click to toggle source
# File lib/scorpio/resource_base.rb, line 153 def all_schema_properties represented_schemas.map(&:described_object_property_names).inject(Set.new, &:|) end
call_operation(operation, call_params: nil, model_attributes: nil)
click to toggle source
# File lib/scorpio/resource_base.rb, line 261 def call_operation(operation, call_params: nil, model_attributes: nil) call_params = JSI.stringify_symbol_keys(call_params) if call_params.respond_to?(:to_hash) model_attributes = JSI.stringify_symbol_keys(model_attributes || {}) request = Scorpio::Request.new(operation) accessor_overridden = -> (accessor) do # an accessor is overridden if the default accessor getter (UnboundMethod) is the same # as the UnboundMethod returned from instance_method on the owner of that instance method. # gotta be the owner since different classes return different UnboundMethod instances for # the same method. for example, referring to models of scorpio/test/blog_scorpio_models.rb # with the server_variables instance method: # Article.instance_method(:server_variables) # => #<UnboundMethod: #<Class:Article>#server_variables> # returns a different UnboundMethod than # Scorpio::ResourceBase.instance_method(:server_variables) # => #<UnboundMethod: #<Class:Scorpio::ResourceBase>#server_variables> # even though they are really the same method (the #owner for both is Scorpio::ResourceBase) inheritable_accessor_defaults[accessor] != self.singleton_class.instance_method(accessor).owner.instance_method(accessor) end # pretty ugly... may find a better way to do this. request.base_url = self.base_url if accessor_overridden.(:base_url) request.server_variables = self.server_variables if accessor_overridden.(:server_variables) request.server = self.server if accessor_overridden.(:server) request.user_agent = self.user_agent if accessor_overridden.(:user_agent) request.faraday_builder = self.faraday_builder if accessor_overridden.(:faraday_builder) request.faraday_adapter = self.faraday_adapter if accessor_overridden.(:faraday_adapter) request.path_params = request.path_template.variables.map do |var| if call_params.respond_to?(:to_hash) && call_params.key?(var) {var => call_params[var]} elsif model_attributes.respond_to?(:to_hash) && model_attributes.key?(var) {var => model_attributes[var]} else {} end end.inject({}, &:update) # assume that call_params must be included somewhere. model_attributes are a source of required things # but not required to be here. if call_params.respond_to?(:to_hash) unused_call_params = call_params.reject { |k, _| request.path_template.variables.include?(k) } if !unused_call_params.empty? other_params = unused_call_params else other_params = nil end else other_params = call_params end if operation.request_schema # TODO deal with model_attributes / call_params better in nested whatever if call_params.nil? request.body_object = request_body_for_schema(model_attributes, operation.request_schema) elsif call_params.respond_to?(:to_hash) body = request_body_for_schema(model_attributes.merge(call_params), operation.request_schema) request.body_object = body.merge(call_params) # TODO else request.body_object = call_params end else if other_params if METHODS_WITH_BODIES.any? { |m| m.to_s == operation.http_method.downcase.to_s } request.body_object = other_params else if other_params.respond_to?(:to_hash) # TODO pay more attention to 'parameters' api method attribute request.query_params = other_params else raise end end end end ur = request.run_ur ur.raise_on_http_error initialize_options = { 'persisted' => true, 'source' => {'operationId' => operation.operationId, 'call_params' => call_params, 'url' => ur.request.uri.to_s}, 'ur' => ur, } response_object_to_instances(ur.response.body_object, initialize_options) end
define_inheritable_accessor(accessor, default_value: nil, default_getter: -> { default_value }
click to toggle source
@param accessor [String, Symbol] the name of the accessor @param default_getter [#to_proc] a proc to provide a default value when no value
has been explicitly set
@param default_value [Object] a default value to return when no value has been
explicitly set. do not pass both :default_getter and :default_value.
@param on_set [#to_proc] callback proc, invoked when a value is assigned
# File lib/scorpio/resource_base.rb, line 20 def define_inheritable_accessor(accessor, default_value: nil, default_getter: -> { default_value }, on_set: nil) # the value before the field is set (overwritten) is the result of the default_getter proc define_singleton_method(accessor, &default_getter) inheritable_accessor_defaults[accessor] = self.singleton_class.instance_method(accessor) # field setter method. redefines the getter, replacing the method with one that returns the # setter's argument (that being inherited to the scope of the define_method(accessor) block define_singleton_method(:"#{accessor}=") do |value| # the setter operates on the singleton class of the receiver (self) singleton_class.instance_exec(value, self) do |value_, klass| # remove a previous getter. NameError is raised if a getter is not defined on this class; # this may be ignored. begin remove_method(accessor) rescue NameError end # getter method define_method(accessor) { value_ } # invoke on_set callback defined on the class if on_set klass.instance_exec(&on_set) end end end end
method_names_by_operation()
click to toggle source
# File lib/scorpio/resource_base.rb, line 211 def method_names_by_operation @method_names_by_operation ||= Hash.new do |h, operation| h[operation] = begin raise(ArgumentError, operation.pretty_inspect) unless operation.is_a?(Scorpio::OpenAPI::Operation) # if Pet is the Scorpio resource class # and Pet.tag_name is "pet" # and operation's operationId is "pet.add" # then the operation's method name on Pet will be "add". # if the operationId is just "addPet" # then the operation's method name on Pet will be "addPet". tag_name_match = tag_name && operation.tags.respond_to?(:to_ary) && # TODO maybe operation.tags.valid? operation.tags.include?(tag_name) && operation.operationId && operation.operationId.match(/\A#{Regexp.escape(tag_name)}\.(\w+)\z/) if tag_name_match method_name = tag_name_match[1] else method_name = operation.operationId end end end end
new(attributes = {}, options = {})
click to toggle source
# File lib/scorpio/resource_base.rb, line 461 def initialize(attributes = {}, options = {}) @attributes = JSI.stringify_symbol_keys(attributes) @options = JSI.stringify_symbol_keys(options) @persisted = !!@options['persisted'] end
openapi_document()
click to toggle source
the openapi document
# File lib/scorpio/resource_base.rb, line 93 def openapi_document nil end
openapi_document=(openapi_document)
click to toggle source
# File lib/scorpio/resource_base.rb, line 100 def openapi_document=(openapi_document) openapi_document = OpenAPI::Document.from_instance(openapi_document) begin singleton_class.instance_exec { remove_method(:openapi_document) } rescue NameError end begin singleton_class.instance_exec { remove_method(:openapi_document_class) } rescue NameError end openapi_document_class = self define_singleton_method(:openapi_document) { openapi_document } define_singleton_method(:openapi_document_class) { openapi_document_class } define_singleton_method(:openapi_document=) do |_| if self == openapi_document_class raise(ArgumentError, "openapi_document may only be set once on #{self.inspect}") else raise(ArgumentError, "openapi_document may not be overridden on subclass #{self.inspect} after it was set on #{openapi_document_class.inspect}") end end # TODO blame validate openapi_document update_dynamic_methods end
openapi_document_class()
click to toggle source
# File lib/scorpio/resource_base.rb, line 96 def openapi_document_class nil end
operation_for_resource_class?(operation)
click to toggle source
# File lib/scorpio/resource_base.rb, line 172 def operation_for_resource_class?(operation) return false unless tag_name return true if operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name) if (operation.request_schemas || []).any? { |s| represented_schemas.include?(s) } return true end return false end
operation_for_resource_instance?(operation)
click to toggle source
# File lib/scorpio/resource_base.rb, line 184 def operation_for_resource_instance?(operation) return false unless operation_for_resource_class?(operation) # define an instance method if the request schema is for this model request_resource_is_self = operation.request_schemas.any? do |request_schema| represented_schemas.include?(request_schema) end # also define an instance method depending on certain attributes the request description # might have in common with the model's schema attributes request_attributes = [] # if the path has attributes in common with model schema attributes, we'll define on # instance method request_attributes |= operation.path_template.variables # TODO if the method request schema has attributes in common with the model schema attributes, # should we define an instance method? #request_attributes |= request_schema && request_schema['type'] == 'object' && request_schema['properties'] ? # request_schema['properties'].keys : [] # TODO if the method parameters have attributes in common with the model schema attributes, # should we define an instance method? #request_attributes |= method_desc['parameters'] ? method_desc['parameters'].keys : [] schema_attributes = represented_schemas.map(&:described_object_property_names).inject(Set.new, &:|) return request_resource_is_self || (request_attributes & schema_attributes.to_a).any? end
request_body_for_schema(object, schema)
click to toggle source
# File lib/scorpio/resource_base.rb, line 350 def request_body_for_schema(object, schema) if object.is_a?(Scorpio::ResourceBase) # TODO request_schema_fail unless schema is for given model type request_body_for_schema(object.attributes, schema) elsif object.is_a?(JSI::PathedNode) request_body_for_schema(object.node_content, schema) else if object.respond_to?(:to_hash) object.map do |key, value| if schema if schema['type'] == 'object' # TODO code dup with response_object_to_instances if schema['properties'].respond_to?(:to_hash) && schema['properties'].key?(key) subschema = schema['properties'][key] include_pair = true else if schema['patternProperties'].respond_to?(:to_hash) _, pattern_schema = schema['patternProperties'].detect do |pattern, _| key =~ Regexp.new(pattern) # TODO map pattern to ruby syntax end end if pattern_schema subschema = pattern_schema include_pair = true else if schema['additionalProperties'] == false include_pair = false elsif [nil, true].include?(schema['additionalProperties']) include_pair = true subschema = nil else include_pair = true subschema = schema['additionalProperties'] end end end elsif schema['type'] request_schema_fail(object, schema) else # TODO not sure include_pair = true subschema = nil end end if include_pair {key => request_body_for_schema(value, subschema)} else {} end end.inject({}, &:update) elsif object.respond_to?(:to_ary) || object.is_a?(Set) object.map do |el| if schema if schema['type'] == 'array' # TODO index based subschema or whatever else works for array subschema = schema['items'] elsif schema['type'] request_schema_fail(object, schema) end end request_body_for_schema(el, subschema) end else # TODO maybe raise on anything not serializable # TODO check conformance to schema, request_schema_fail if not object end end end
request_schema_fail(object, schema)
click to toggle source
# File lib/scorpio/resource_base.rb, line 420 def request_schema_fail(object, schema) # TODO blame end
response_object_to_instances(object, initialize_options = {})
click to toggle source
# File lib/scorpio/resource_base.rb, line 424 def response_object_to_instances(object, initialize_options = {}) if object.is_a?(JSI::Base) models = object.jsi_schemas.map { |schema| models_by_schema[schema] } if models.size == 0 model = nil elsif models.size == 1 model = models.first else raise(Scorpio::OpenAPI::Error, "multiple models indicated by response JSI. models: #{models.inspect}; jsi: #{jsi.pretty_inspect.chomp}") end end if object.respond_to?(:to_hash) out = JSI::Typelike.modified_copy(object) do |_object| mod = object.map do |key, value| {key => response_object_to_instances(value, initialize_options)} end.inject({}, &:update) mod = mod.node_content if mod.is_a?(JSI::PathedNode) mod end if model model.new(out, initialize_options) else out end elsif object.respond_to?(:to_ary) JSI::Typelike.modified_copy(object) do object.map do |element| response_object_to_instances(element, initialize_options) end end else object end end
tag_name()
click to toggle source
# File lib/scorpio/resource_base.rb, line 125 def tag_name nil end
tag_name=(tag_name)
click to toggle source
# File lib/scorpio/resource_base.rb, line 129 def tag_name=(tag_name) unless tag_name.respond_to?(:to_str) raise(TypeError, "tag_name must be a string; got: #{tag_name.inspect}") end tag_name = tag_name.to_str begin singleton_class.instance_exec { remove_method(:tag_name) } rescue NameError end define_singleton_method(:tag_name) { tag_name } define_singleton_method(:tag_name=) do |tag_name| unless tag_name == self.tag_name raise(ArgumentError, "tag_name may not be overridden (to #{tag_name.inspect}). it is been set to #{self.tag_name.inspect}") end end update_dynamic_methods end
update_class_and_instance_api_methods()
click to toggle source
# File lib/scorpio/resource_base.rb, line 237 def update_class_and_instance_api_methods openapi_document.paths.each do |path, path_item| path_item.each do |http_method, operation| next unless operation.is_a?(Scorpio::OpenAPI::Operation) method_name = method_names_by_operation[operation] if method_name # class method if operation_for_resource_class?(operation) && !respond_to?(method_name) define_singleton_method(method_name) do |call_params = nil| call_operation(operation, call_params: call_params) end end # instance method if operation_for_resource_instance?(operation) && !method_defined?(method_name) define_method(method_name) do |call_params = nil| call_operation(operation, call_params: call_params) end end end end end end
update_dynamic_methods()
click to toggle source
# File lib/scorpio/resource_base.rb, line 148 def update_dynamic_methods update_class_and_instance_api_methods update_instance_accessors end
update_instance_accessors()
click to toggle source
# File lib/scorpio/resource_base.rb, line 157 def update_instance_accessors all_schema_properties.each do |property_name| unless method_defined?(property_name) define_method(property_name) do self[property_name] end end unless method_defined?(:"#{property_name}=") define_method(:"#{property_name}=") do |value| self[property_name] = value end end end end
Public Instance Methods
[](key)
click to toggle source
# File lib/scorpio/resource_base.rb, line 474 def [](key) @attributes[key] end
[]=(key, value)
click to toggle source
# File lib/scorpio/resource_base.rb, line 478 def []=(key, value) @attributes[key] = value end
as_json(*opt)
click to toggle source
# File lib/scorpio/resource_base.rb, line 508 def as_json(*opt) JSI::Typelike.as_json(@attributes, *opt) end
call_api_method(method_name, call_params: nil)
click to toggle source
# File lib/scorpio/resource_base.rb, line 482 def call_api_method(method_name, call_params: nil) operation = self.class.method_names_by_operation.invert[method_name] || raise(ArgumentError) call_operation(operation, call_params: call_params) end
call_operation(operation, call_params: nil)
click to toggle source
# File lib/scorpio/resource_base.rb, line 487 def call_operation(operation, call_params: nil) response = self.class.call_operation(operation, call_params: call_params, model_attributes: self.attributes) # if we're making a POST or PUT and the request schema is this resource, we'll assume that # the request is persisting this resource request_resource_is_self = operation.request_schema && self.class.represented_schemas.include?(operation.request_schema) if @options['ur'].is_a?(Scorpio::Ur) response_schema = @options['ur'].response.response_schema end response_resource_is_self = response_schema && self.class.represented_schemas.include?(response_schema) if request_resource_is_self && %w(put post).include?(operation.http_method.to_s.downcase) @persisted = true if response_resource_is_self @attributes = response.attributes end end response end
inspect()
click to toggle source
# File lib/scorpio/resource_base.rb, line 512 def inspect "\#<#{self.class.inspect} #{attributes.inspect}>" end
jsi_fingerprint()
click to toggle source
# File lib/scorpio/resource_base.rb, line 529 def jsi_fingerprint {class: self.class, attributes: JSI::Typelike.as_json(@attributes)} end
persisted?()
click to toggle source
# File lib/scorpio/resource_base.rb, line 470 def persisted? @persisted end
pretty_print(q)
click to toggle source
# File lib/scorpio/resource_base.rb, line 515 def pretty_print(q) q.instance_exec(self) do |obj| text "\#<#{obj.class.inspect}" group_sub { nest(2) { breakable ' ' pp obj.attributes } } breakable '' text '>' end end