Skip to content

Commit

Permalink
Merge pull request #6 from Sage/feature_update_contract_parameter_com…
Browse files Browse the repository at this point in the history
…parison

Contract Comparison Update.
  • Loading branch information
vaughanbrittonsage authored Mar 12, 2018
2 parents 9ddcf5d + 7a74b08 commit b3f4074
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 90 deletions.
8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
source "http://rubygems.org"
gemspec
gem 'pry'

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ For example:
container.register(:logger, TerminalLogger, true)
end
def is_valid?
def valid?
Rails.env.development?
end
end
Expand Down
128 changes: 72 additions & 56 deletions lib/sinject/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@ class << self
end

def initialize(singleton=true)
@store = []
if singleton
Sinject::Container.instance = self
end
@store = {}
Sinject::Container.instance = self if singleton
end

# Check if an object has been registered with the container.
#
# Example:
# >> Sinject::Container.instance.is_registered? :object_key
# >> Sinject::Container.instance.registered? :object_key
# => true
#
# Arguments:
# key: (Symbol)
def registered?(key)
@store.has_key?(key)
end
# @deprecated: Use registered? method instead
def is_registered?(key)
!@store.select { |i| i.key == key}.empty?
puts "[#{self.class}] - #is_registered? method is deprecated please use #registered? instead."
registered?(key)
end

# Register an object with the container.
Expand All @@ -38,22 +41,15 @@ def is_registered?(key)
# class_name: (ClassName)
# single_instance: (Boolean)
def register(options = {}, &initialize_block)
raise Sinject::DependencyRegistrationKeyNotSpecifiedException.new unless options.has_key?(:key)

if(!options.has_key?(:key))
raise Sinject::DependencyRegistrationKeyNotSpecifiedException.new
end

if(!options.has_key?(:class))
raise Sinject::DependencyRegistrationClassNotSpecifiedException.new
end
raise Sinject::DependencyRegistrationClassNotSpecifiedException.new unless options.has_key?(:class)

key = options[:key]
dependency_class_name = options[:class]

#check if a dependency has already been registered for this key.
if is_registered?(key)
raise Sinject::DependencyRegistrationException.new(key)
end
# check if a dependency has already been registered for this key.
raise Sinject::DependencyRegistrationException.new(key) if registered?(key)

single_instance = false
contract_class_name = nil
Expand All @@ -66,19 +62,17 @@ def register(options = {}, &initialize_block)
contract_class_name = options[:contract]
end

#check if a contract has been specified
if contract_class_name != nil
#validate the dependency class against the contract
validate_contract(dependency_class_name, contract_class_name)
end
# Validate the dependency class against the contract if a contract has been specified
validate_contract(dependency_class_name, contract_class_name) unless contract_class_name.nil?

item = Sinject::ContainerItem.new
item.key = key
item.single_instance = single_instance
item.class_name = dependency_class_name
item.initialize_block = initialize_block

@store.push(item)
@store[item.key] = item
true
end

# Get an object from the container.
Expand All @@ -90,33 +84,32 @@ def register(options = {}, &initialize_block)
# Arguments:
# key: (Symbol)
def get(key)
#get the dependency from the container store for the specified key
items = @store.select { |i| i.key == key}
if !items.empty?
item = items.first

#check if the item has been registered as a single instance item.
# get the dependency from the container store for the specified key
item = @store[key]
if !item.nil?
# check if the item has been registered as a single instance item.
if item.single_instance == true
#check if the instance needs to be created
if item.instance == nil
item.instance = create_instance(item)
end
# check if the instance needs to be created
item.instance = create_instance(item) if item.instance.nil?

return item.instance
else
return create_instance(item)
end
else
#no dependency has been registered for the specified key, attempt to convert the key into a class name and initialize it.
# no dependency has been registered for the specified key,
# attempt to convert the key into a class name and initialize it.
class_name = "#{key}".split('_').collect(&:capitalize).join
puts "[Sinject] - WARNING: No registered dependency could be found for key: #{key}. Attempting to load class: #{class_name}."
puts "[#{self.class}] - WARNING: No registered dependency could be found for key: #{key}. " \
"Attempting to load class: #{class_name}."
Object.const_get(class_name).new
end
end

def load_groups
Sinject::DependencyGroup.descendants.each do |g|
group = g.new
if group.is_valid?
if (group.respond_to?(:valid?) && group.valid?) || (group.respond_to?(:is_valid?) && group.is_valid?)
group.register(self)
end
end
Expand All @@ -125,51 +118,74 @@ def load_groups
private

def validate_contract(dependency_class, contract_class)

#get the methods defined for the contract
# get the methods defined for the contract
contract_methods = (contract_class.instance_methods - Object.instance_methods)
#get the methods defined for the dependency
# get the methods defined for the dependency
dependency_methods = (dependency_class.instance_methods - Object.instance_methods)
#calculate any methods specified in the contract that are not specified in the dependency
# calculate any methods specified in the contract that are not specified in the dependency
missing_methods = contract_methods - dependency_methods

if !missing_methods.empty?
raise Sinject::DependencyContractMissingMethodsException.new(missing_methods)
end

#loop through each contract method
# loop through each contract method
contract_methods.each do |method|
# get the contract method parameters
contract_params = contract_class.instance_method(method).parameters.map{ |p| { type: p[0], name: p[1] } }

#get the contract method parameters
cmp = contract_class.instance_method(method).parameters
#get teh dependency method parameters
dmp = dependency_class.instance_method(method).parameters
# get the dependency method parameters
dependency_params = dependency_class.instance_method(method).parameters.map{ |p| { type: p[0], name: p[1] } }

#check if the parameters match for both methods
if cmp != dmp
raise Sinject::DependencyContractInvalidParametersException.new(method, cmp)
errors = []

contract_params.each do |cp|
dp = dependency_params.detect { |p| p[:name] == cp[:name] }
if dp.nil? || !match?(cp, dp)
errors << cp[:name]
end
end

dependency_params.each do |dp|
cp = contract_params.detect { |p| p[:name] == dp[:name] }
if cp.nil?
errors << dp[:name]
end
end

# check if any parameter errors
if errors.length > 0
raise Sinject::DependencyContractInvalidParametersException.new(method, errors)
end
end
end

def match?(contract, dependency)
return true if contract[:type] == dependency[:type]
return true if contract[:type] == :req && dependency[:type] == :opt
return true if contract[:type] == :keyreq && dependency[:type] == :key
return false
end

def create_instance(item)
# this method is called to get a standard param type for comparison purposes
def param_type(type)
return :arg if type == :opt || type == :req
return :key if type == :keyreq || type == :key
end

#check if a custom initializer block has been specified
def create_instance(item)
# check if a custom initializer block has been specified
if item.initialize_block != nil
#call the block to create the dependency instance
# call the block to create the dependency instance
instance = item.initialize_block.call

#verify the block created the expected dependency type
if !instance.is_a?(item.class_name)
raise Sinject::DependencyInitializeException.new(item.class_name)
end
# verify the block created the expected dependency type
raise Sinject::DependencyInitializeException.new(item.class_name) unless instance.is_a?(item.class_name)
else
instance = item.class_name.new
end

instance
end
end
end
end
9 changes: 1 addition & 8 deletions lib/sinject/dependency_group.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
module Sinject
class DependencyGroup

def register(container)

end

def is_valid?
return true
end

def self.descendants
ObjectSpace.each_object(Class).select { |klass| klass < self }
end

end
end
end
2 changes: 1 addition & 1 deletion script/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ docker-compose up -d

spec_path=spec/$1

docker exec -it gem_test_runner bash -c "cd gem_src && bundle install && bundle exec rspec ${spec_path}"
docker exec -it gem_test_runner bash -c "cd gem_src && bundle install && bundle exec rspec $*"
6 changes: 3 additions & 3 deletions sinject.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ if ENV['TRAVIS_TAG'] != nil
version = ENV['TRAVIS_TAG']
end

#if the tag version starts with v (e.g. vx.x.x)
# if the tag version starts with v (e.g. vx.x.x)
if version.downcase.match /^v/
#trim the v and set the version to x.x.x
# trim the v and set the version to x.x.x
version = version.dup
version.slice!(0)
elsif ENV['TRAVIS_TAG'] != nil && ENV['TRAVIS_TAG'] != ''
version = "0.0.0.#{ENV['TRAVIS_TAG']}"
else
#otherwise it is not a valid release tag so set the version 0.0.0 as it not being released.
# otherwise it is not a valid release tag so set the version 0.0.0 as it not being released.
version = '0.0.0'
end

Expand Down
4 changes: 1 addition & 3 deletions spec/sinject/sinject_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,10 @@
end

it 'should NOT load dependencies from invalid dependencygroups' do

container = Sinject::Container.new(false)
container.load_groups

expect(container.is_registered?(:logger)).to eq(false)

end

context 'when a duplicate dependency key is registered' do
Expand All @@ -146,4 +144,4 @@
end


end
end
14 changes: 4 additions & 10 deletions spec/sinject/test_objects/test_classes.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
require_relative '../../../lib/sinject'
class SingleInstance

end

class MultiInstance

end

class HelloWorld
Expand All @@ -29,25 +27,21 @@ class ObjectWithDependency
end

class CustomLogger
def write

def write(text, format=:json, location:, foo: 'bar')
end
end
class LoggerContract
def write

def write(text, format, location:, foo:)
end
end

class CacheControlContract
def set(key, value)

end
end

class RedisCacheControl
def set(key, value, expires)

end
end

Expand All @@ -57,7 +51,7 @@ def register(container)
container.register({ :key => :goodbye_world, :class => GoodbyeWorld, :singleton => true})
end

def is_valid?
def valid?
return true
end
end
Expand All @@ -67,7 +61,7 @@ def register(container)
container.register({ :key => :logger, :class => CustomLogger, :singleton => true})
end

def is_valid?
def valid?
return false
end
end
Expand Down

0 comments on commit b3f4074

Please sign in to comment.