module Smash module CloudPowers module LogicHelp # Sets an Array of instance variables, individually to a value that a # user given block returns. # # Parameters # # * keys +Array+ # * * each object will be used as the name for the instance variable that # your block returns # +block+ (optional) # * this block is called for each object in the Array and is used as the value # for the instance variable that is being named and created for each key # Returns +Array+ - each object will either be the result of # #instance_variable_set(key, value) => +value+ # or instance_variable_get(key) # Example # keys = ['foo', 'bar', 'yo'] # # attr_map!(keys) { |key| sleep 1; "#{key}:#{Time.now.to_i}" } # # => ['foo:1475434058', 'bar:1475434059', 'yo:1475434060'] # # puts @bar # # => 'bar:1475434059' def attr_map(attributes = self.i_vars) attributes = [attributes, nil] unless attributes.respond_to? :map attributes.inject(self) do |this, (attribute, before_value)| results = if block_given? first_place, second_place = yield attribute, before_value second_place.nil? ? [attribute, first_place] : [first_place, second_place] else [attribute, before_value] end this.instance_variable_set(to_i_var(results.first), results.last) this end end # Does its best job at guessing where this method was called from, in terms # of where it is located on the file system. It helps track down where a # project root is etc. # # Returns # * +Pathname+ # # Notes # * Uses ::#caller() to figure out where the request came from def called_from sanitized_path_string = caller.first.gsub(/:.*/,'') calling_file = File.expand_path(sanitized_path_string) to_realpath(calling_file).parent end # Gather up this object's instance variables and create a +Hash+ from the # name/value pairs # # Returns # * +Hash+ # # Example # 5class A; attr_accessor :eh; end # a.i_var_hash # => { eh: nil } def i_var_hash self.instance_variables.inject({}) do |hash, (k,v)| hash[k.to_s.gsub('@', '')] = self.instance_variable_get to_i_var(k) hash end end # Create an attr_accessor feeling getter and setter for an instance # variable. The method doesn't create a getter or setter if it is already # defined. # # Parameters # * base_name +String+ - the name, without the '@' symbol # # ok # add_instance_attr_accessor('my_variable_name', my_value) # => # # not ok # add_instance_attr_accessor('@#!!)', my_value) # => no new instance variable found # * value +Object+ - the actual instance variable that matches the +base_name+ # # Returns # * the value of the instance variable that matches the +base_name+ (first) argument # # Notes # * if a matching getter or setter method can be found, this method won't # stomp on it. nothing happens, in that case # * if an appropriately named instance variable can't be found, the getter # method will return nil until you set it again. # * it is the responsibility of you and me to make sure our variable names # are valid, i.e. proper Ruby instance variable names def instance_attr_accessor(base_name) i_var_name = to_i_var(base_name) getter_signature = to_snake(base_name) setter_signature = "#{getter_signature}=" unless respond_to? getter_signature define_singleton_method(getter_signature) do instance_variable_get(i_var_name) end end unless respond_to? setter_signature define_singleton_method(setter_signature) do |argument| instance_variable_set(i_var_name, argument) end end end # Lets you retry a piece of logic with 1 second sleep in between attempts # until another bit of logic does what it's supposed to, kind of like # continuing to poll something and doing something when a package is ready # to be taken and processed. # # Parameters # * allowed_attempts +Number+|+Infinity(default)+ - The number of times # the loop should be allowed to...well, loop, before a failed retry occurs. # * &test +Block+ - A predicate method or block of code that is callable # is used to test if the block being retried is successful yet. # * [] # # Example # check_stuff = lambda { |params| return true } # smart_retry(3, check_stuff(params)) { do_stuff_that_needs_to_be_checked } def smart_retry(test, allowed_attempts = Float::INFINITY) result = yield if block_given? tries = 1 until test.call(result) || tries >= allowed_attempts result = yield if block_given? tries += 1 sleep 1 end end # This method provides a default overrideable message body for things like # basic status updates. # # Parameters # * instanceId +Hash+ # # Notes # * camel casing is used on the keys because most other languages prefer # that and it's not a huge problem in ruby. Besides, there's some other # handy methods in this module to get you through those issues, like # +#to_snake()+ and or +#modify_keys_with()+ def update_message_body(opts = {}) # TODO: Better implementation of merging message bodies and config needed unless opts.kind_of? Hash update = opts.to_s opts = {} opts[:extraInfo] = { message: update } end updated_extra_info = opts.delete(:extraInfo) || {} { instanceId: @instance_id || 'none-aquired', type: 'status-update', content: 'running', extraInfo: updated_extra_info }.merge(opts) end end end end