Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Adding support for subcollections and related subresources #80

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a21b621
Adding support for subcollections and related subresources
abellotti Jan 26, 2017
944b385
Refactorying/Drying up
abellotti Feb 2, 2017
58456c7
Adding rspecs - options, collections and resources
abellotti Feb 3, 2017
86572d3
Adding rspecs - subcollections
abellotti Feb 7, 2017
1e4c70e
Rspecs - Updated test to used action specific responses
abellotti Feb 8, 2017
2ba97ed
Adding rspecs - subresources
abellotti Feb 8, 2017
52e3387
Minor Alignment changes, leveraging subclass for collection spec.
abellotti Feb 8, 2017
a720dab
Updated PR specs to reflect the latest query enhancements made
abellotti Apr 2, 2018
4ee881d
Assure that all mixins are properly namespaced
abellotti Apr 2, 2018
f2c4814
Common methods each and self.subclass for collections and subcollections
abellotti Apr 2, 2018
640628c
Fully qualify the mixins modules as ManageIQ::API::Client::*
abellotti Apr 3, 2018
0e28739
Simplified filters_from_query_relation method.
abellotti Apr 3, 2018
b374164
Removed duplicated code from collection.rb. Those methods were
abellotti Apr 3, 2018
d9d6859
Simplifying order_parameters_from_query_relation. no need to dup opti…
abellotti Apr 3, 2018
a9cfa51
Adding support for nested resource/subcollection queries.
abellotti Apr 12, 2018
b620767
Only checks for subcollection options if defined as a primary collection
abellotti Apr 12, 2018
64cccc6
removing subresource.rb and updated files/specs accordingly
abellotti Apr 12, 2018
0dc969d
Removing resource_action_mixin.rb since it's only used by resource.rb
abellotti Apr 12, 2018
bcce855
Made Subcollection a subclass of Collection and removed the
abellotti Apr 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/manageiq/api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require "manageiq/api/client/client"
require "manageiq/api/client/mixins/action_mixin"
require "manageiq/api/client/mixins/custom_inspect_mixin"
require "manageiq/api/client/mixins/queryable_mixin"

require "manageiq/api/client/api"
require "manageiq/api/client/action"
Expand All @@ -25,4 +26,5 @@
require "manageiq/api/client/product_info"
require "manageiq/api/client/resource"
require "manageiq/api/client/server_info"
require "manageiq/api/client/subcollection"
require "manageiq/api/client/version"
2 changes: 1 addition & 1 deletion lib/manageiq/api/client/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Authentication
}.freeze

CUSTOM_INSPECT_EXCLUSIONS = [:@password].freeze
include CustomInspectMixin
include ManageIQ::API::Client::CustomInspectMixin

def initialize(options = {})
@user, @password = fetch_credentials(options)
Expand Down
142 changes: 33 additions & 109 deletions lib/manageiq/api/client/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ module ManageIQ
module API
class Client
class Collection
include ActionMixin
include ManageIQ::API::Client::ActionMixin
include Enumerable
include QueryRelation::Queryable

CUSTOM_INSPECT_EXCLUSIONS = [:@client].freeze
include CustomInspectMixin
include ManageIQ::API::Client::QueryableMixin

ACTIONS_RETURNING_RESOURCES = %w(create query).freeze

CUSTOM_INSPECT_EXCLUSIONS = [:@client].freeze
include ManageIQ::API::Client::CustomInspectMixin

attr_reader :client

attr_reader :name
Expand All @@ -25,35 +25,27 @@ def initialize(client, collection_spec)
clear_actions
end

def each(&block)
all.each(&block)
def get(options = {})
options[:expand] = (String(options[:expand]).split(",") | %w(resources)).join(",")
options[:filter] = Array(options[:filter]) if options[:filter].is_a?(String)
result_hash = client.get(name, options)
fetch_actions(result_hash)
klass = ManageIQ::API::Client::Resource.subclass(name)
result_hash["resources"].collect do |resource_hash|
klass.new(self, resource_hash)
end
end

# find(#) returns the object
# find([#]) returns an array of the object
# find(#, #, ...) or find([#, #, ...]) returns an array of the objects
def find(*args)
request_array = args.size == 1 && args[0].kind_of?(Array)
args = args.flatten
case args.size
when 0
raise "Couldn't find resource without an 'id'"
when 1
res = limit(1).where(:id => args[0]).to_a
raise "Couldn't find resource with 'id' #{args}" if res.blank?
request_array ? res : res.first
else
raise "Multiple resource find is not supported" unless respond_to?(:query)
query(args.collect { |id| { "id" => id } })
end
def options
@collection_options ||= CollectionOptions.new(client.options(name))
end

def find_by(args)
limit(1).where(args).first
def each(&block)
all.each(&block)
end

def pluck(*attrs)
select(*attrs).to_a.pluck(*attrs)
def self.defined?(name)
const_defined?(name.camelize, false)
end

def self.subclass(name)
Expand All @@ -66,32 +58,6 @@ def self.subclass(name)
end
end

def get(options = {})
options[:expand] = (String(options[:expand]).split(",") | %w(resources)).join(",")
options[:filter] = Array(options[:filter]) if options[:filter].is_a?(String)
result_hash = client.get(name, options)
fetch_actions(result_hash)
klass = ManageIQ::API::Client::Resource.subclass(name)
result_hash["resources"].collect do |resource_hash|
klass.new(self, resource_hash)
end
end

def search(mode, options)
options[:limit] = 1 if mode == :first
result = get(parameters_from_query_relation(options))
case mode
when :first then result.first
when :last then result.last
when :all then result
else raise "Invalid mode #{mode} specified for search"
end
end

def options
@collection_options ||= CollectionOptions.new(client.options(name))
end

private

def method_missing(sym, *args, &block)
Expand All @@ -108,73 +74,31 @@ def respond_to_missing?(sym, *_)
action_defined?(sym) || super
end

def parameters_from_query_relation(options)
api_params = {}
[:offset, :limit].each { |opt| api_params[opt] = options[opt] if options[opt] }
api_params[:attributes] = options[:select].join(",") if options[:select].present?
if options[:where]
api_params[:filter] ||= []
api_params[:filter] += filters_from_query_relation("=", options[:where])
end
if options[:not]
api_params[:filter] ||= []
api_params[:filter] += filters_from_query_relation("!=", options[:not])
end
if options[:order]
order_parameters_from_query_relation(options[:order]).each { |param, value| api_params[param] = value }
end
api_params
end

def filters_from_query_relation(condition, option)
filters = []
option.each do |attr, values|
Array(values).each do |value|
value = "'#{value}'" if value.kind_of?(String) && !value.match(/^(NULL|nil)$/i)
filters << "#{attr}#{condition}#{value}"
end
end
filters
end

def order_parameters_from_query_relation(option)
query_relation_option =
if option.kind_of?(Array)
option.each_with_object({}) { |name, hash| hash[name] = "asc" }
else
option.dup
end

res_sort_by = []
res_sort_order = []
query_relation_option.each do |sort_attr, sort_order|
res_sort_by << sort_attr
sort_order =
case sort_order
when /^asc/i then "asc"
when /^desc/i then "desc"
else raise "Invalid sort order #{sort_order} specified for attribute #{sort_attr}"
end
res_sort_order << sort_order
end
{ :sort_by => res_sort_by.join(","), :sort_order => res_sort_order.join(",") }
end

def exec_action(name, *args, &block)
action = find_action(name)
body = action_body(action.name, *args, &block)
bulk_request = body.key?("resources")
res = client.send(action.method, URI(action.href)) { body }
if ACTIONS_RETURNING_RESOURCES.include?(action.name) && res.key?("results")
klass = ManageIQ::API::Client::Resource.subclass(self.name)
res = res["results"].collect { |resource_hash| klass.new(self, resource_hash) }
res = results_to_objects(res["results"], klass)
res = res[0] if !bulk_request && res.size == 1
else
res = res["results"].collect { |result| action_result(result) }
end
res
end

def results_to_objects(results, klass)
results.collect do |resource_hash|
if ManageIQ::API::Client::ActionResult.an_action_result?(resource_hash)
ManageIQ::API::Client::ActionResult.new(resource_hash)
else
klass.new(self, resource_hash)
end
end
end

def action_body(action_name, *args, &block)
args = args.flatten
args = args.first if args.size == 1 && args.first.kind_of?(Hash)
Expand All @@ -195,8 +119,8 @@ def action_body(action_name, *args, &block)
body
end

def query_actions
result_hash = client.get(name, :limit => 1)
def query_actions(href = name)
result_hash = client.get(href, :limit => 1)
fetch_actions(result_hash)
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/manageiq/api/client/collection_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ class CollectionOptions
attr_reader :attributes
attr_reader :virtual_attributes
attr_reader :relationships
attr_reader :subcollections
attr_reader :data

def initialize(options = {})
@attributes, @virtual_attributes, @relationships, @data =
options.values_at("attributes", "virtual_attributes", "relationships", "data")
@attributes, @virtual_attributes, @relationships, @subcollections, @data =
options.values_at("attributes", "virtual_attributes", "relationships", "subcollections", "data")
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/manageiq/api/client/mixins/action_mixin.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module ActionMixin
module ManageIQ::API::Client::ActionMixin
extend ActiveSupport::Concern

private
Expand Down
2 changes: 1 addition & 1 deletion lib/manageiq/api/client/mixins/custom_inspect_mixin.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module CustomInspectMixin
module ManageIQ::API::Client::CustomInspectMixin
extend ActiveSupport::Concern

def inspect
Expand Down
93 changes: 93 additions & 0 deletions lib/manageiq/api/client/mixins/queryable_mixin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
module ManageIQ::API::Client::QueryableMixin
include QueryRelation::Queryable

# find(#) returns the object
# find([#]) returns an array of the object
# find(#, #, ...) or find([#, #, ...]) returns an array of the objects
def find(*args)
request_array = args.size == 1 && args[0].kind_of?(Array)
args = args.flatten
case args.size
when 0
raise "Couldn't find resource without an 'id'"
when 1
res = limit(1).where(:id => args[0]).to_a
raise "Couldn't find resource with 'id' #{args}" if res.blank?
request_array ? res : res.first
else
raise "Multiple resource find is not supported" unless respond_to?(:query)
query(args.collect { |id| { "id" => id } })
end
end

def find_by(args)
limit(1).where(args).first
end

def pluck(*attrs)
select(*attrs).to_a.pluck(*attrs)
end

def search(mode, options)
options[:limit] = 1 if mode == :first
result = get(parameters_from_query_relation(options))
case mode
when :first then result.first
when :last then result.last
when :all then result
else raise "Invalid mode #{mode} specified for search"
end
end

private

def parameters_from_query_relation(options)
api_params = {}
[:offset, :limit].each { |opt| api_params[opt] = options[opt] if options[opt] }
api_params[:attributes] = options[:select].join(",") if options[:select].present?
if options[:where]
api_params[:filter] ||= []
api_params[:filter] += filters_from_query_relation("=", options[:where])
end
if options[:not]
api_params[:filter] ||= []
api_params[:filter] += filters_from_query_relation("!=", options[:not])
end
if options[:order]
order_parameters_from_query_relation(options[:order]).each { |param, value| api_params[param] = value }
end
api_params
end

def filters_from_query_relation(condition, option)
option.collect do |attr, values|
Array(values).collect do |value|
value = "'#{value}'" if value.kind_of?(String) && !value.match(/^(NULL|nil)$/i)
"#{attr}#{condition}#{value}"
end
end.flatten
end

def order_parameters_from_query_relation(option)
query_relation_option =
if option.kind_of?(Array)
option.each_with_object({}) { |name, hash| hash[name] = "asc" }
else
option
end

res_sort_by = []
res_sort_order = []
query_relation_option.each do |sort_attr, sort_order|
res_sort_by << sort_attr
sort_order =
case sort_order
when /^asc/i then "asc"
when /^desc/i then "desc"
else raise "Invalid sort order #{sort_order} specified for attribute #{sort_attr}"
end
res_sort_order << sort_order
end
{ :sort_by => res_sort_by.join(","), :sort_order => res_sort_order.join(",") }
end
end
Loading