Accessing local variables in the calling context - ruby

In my code, i have several methods using the following pattern
def my_method only: [], ignore: []
something('textvalue') if (only.empty? && ignore.empty?) || (only.any? && only.include?('textvalue')) || (ignore.any? && !ignore.include?('textvalue'))
end
In other word, i can choose to filter the results either by specifying only or ignore, depending on which one is more convenient in the context.
I would like a way to declare a helper want that access the onlyand ignore local parameters without having to specify them each time, the result would ideally look like this:
def my_method only: [], ignore: []
something('textvalue') if want('textvalue')
end
This helper would be used in several different methods, classes, etc. It would somehow check the local variables at the calling point, validate that only and ignore exists, and then check if the parameter is wanted or not.
Is it possible to access the calling stack and look at the local variables there?

There is a gem binding_of_caller which can do this. Install the gem and then do
require 'binding_of_caller'
def my_method only: [], ignore: []
something('textvalue') if want('textvalue')
end
def want(value)
only = binding.of_caller(1).local_variable_get(:only)
ignore = binding.of_caller(1).local_variable_get(:ignore)
(only.empty? && ignore.empty?) || (only.any? && only.include?(value)) || (ignore.any? && !ignore.include?(value))
end
But this is a bad thing to do as it creates very high coupling. It's really not good design. If you want to do this for experimentation and playing around, fair enough, but don't do it in production.

You can use ruby method definition to do this
def my_method val, only: [], ignore: [], want: ->(val) { ((only.empty? && ignore.empty?) || (only.any? && only.include?(val))) }
something(val) if want.call(val)
end
my_method 'value', only: ['value2']
=> false

In circumstances such as this, couldn't you just pass the args to want?
def my_method only: [], ignore: []
something('textvalue') if want?('textvalue', only, ignore)
end
def want?(string, only, ignore)
(only.empty? && ignore.empty?) || (only.any? && only.include?(string)) || (ignore.any? && !ignore.include?(string))
end
Doesn't seem to be any need for it to be more complex than that?
That is a lot simpler to manage and for others to deal with going forward - which I'd consider far more important than avoiding passing a couple of args to a method.

Related

Is multiple include?() arguments in ruby possible?

def coffee_drink?(drink_list)
drink_list.include?("coffee") ? true : drink_list.include?("espresso") ? true : false
end
I am learning Ruby with TOP and am looking to check for more than a single argument with the include function. I don't think my solution is too bad but surely there is a better solution I am just unable to find.
e.g. include?("ABC", "CBA) and include?("ABC" || "CBA") wont work.
def coffee_drink?(drink_list)
%w(coffee espresso).any? { |drink| drink_list.include?(drink) }
end
or
def coffee_drink?(drink_list)
(%w(coffee espresso) & drink_list).any?
end
note that your version could be rewritten like this
def coffee_drink?(drink_list)
drink_list.include?("coffee") || drink_list.include?("espresso")
end

Loop through variables in different classes to match string in Ruby

I have a ruby (sinatra) app that I am working on, and my input is a url and if verbose or not (true or false), so basically like this:
The url would look like this: http://localhost:4567/git.company.com&v=false for example.
And the code to fetch those is this:
get '/:url' do |tool_url|
url = params[:url].to_s
is_verbose = params[:v].to_s
I have different classes separated in different files and I'm including them into my main script like this:
Dir["#{File.dirname(__FILE__)}/lib/*.rb"].each { |f| require(f) }
(And a sample file would be something like this), gitlab.rb:
class Gitlab
$gitlab_token = 'TOKEN_GOES_HERE'
def initialize(url, v)
##regex =~ /git.company.com/
##gitlab_url = url
##is_verbose = v
end
def check_gitlab(gitlab_url, is_verbose)
_gitlab_overall = '/health_check.json?token=#{gitlab_token}'
_gitlab_cache = '/health_check/cache.json?token=#{gitlab_token}'
_gitlab_database = '/health_check/database.json?token=#{gitlab_token}'
_gitlab_migrations = '/health_check/migrations.json?token=#{gitlab_token}'
unless is_verbose = true
CheckString.check_string_from_page('https://' + gitlab_url + gitlab_overall, 'success')
else
end
end
end
Now, I want to be able to dynamically know which "class" to use to do a certain job based on the URL that's entered by the user, so my idea was to iterate through those classes looking for a particular variable to match with the input.
I need guidance in this because I've been stuck on this for quite some time now; I've tried so many things that I can think of, but none worked.
Disclaimer: Please bear with me here, because I'm very new to Ruby and I'm not that great in OOP languages (haven't really practiced them that much).
EDIT: I'm open to any suggestion, like if there's a different logic that's better than this, please do let me know.
Make a hash { Regexp ⇒ Class }:
HASH = {
/git.company.com/ => Gitlab,
/github.com/ => Github
}
and then do:
handler = HASH.detect { |k, _| k =~ url }.last.new
The above will give you an instance of the class you wanted.
Sidenotes:
is_verbose = params[:v].to_s always results in is_verbose set to truthy value, check for params[:v].to_s == "true"
is_verbose = true is an assignment, you wanted to use just unless is_verbose.
To make it runtime-resolving, force the plugins to a) include Plugin and b) declare resolve method. Plugin module should define a callback hook:
module Plugin
def self.included(base)
Registry::HASH[-> { base.resolve }] = base
end
end
resolve method should return a regexp, the lambda is here to make it resolved on parsing stage:
class PluginImpl
include Plugin
def resolve
/git.company.com/
end
end
And then match when needed:
handler = HASH.detect { |k, _| k.() =~ url }.last.new
Other way round would be to use ObjectSpace to detect classes, including the module, or declare the TracePoint on base in included callback to provide a direct map, but all this is overcomplicating.

What are the equivalent to Lodash's get and set in Ruby?

I would like to use something similar to Lodash's get and set, but in Ruby instead of JavaScript. I tried few searches but I can't find anything similar.
Lodash's documentation will probably explain it in a better way, but it's getting and setting a property from a string path ('x[0].y.z' for example). If the full path doesn't exist when setting a property, it is automatically created.
Lodash Set
Lodash Get
I eventually ported Lodash _.set and _.get from JavaScript to Ruby and made a Gem.
Ruby 2.3 introduces the new safe navigator operator for getting nested/chained values:
x[0]&.y&.z #=> result or nil
Otherwise, Rails monkey patches all objects with try(…), allowing you to:
x[0].try(:y).try(:z) #=> result or nil
Setting is a bit harder, and I'd recommend ensuring you have the final object before attempting to set a property, e.g.:
if obj = x[0]&.y&.z
z.name = "Dr Robot"
end
You can use the Rudash Gem that comes with most of the Lodash utilities, and not only the _.get and _.set.
Sometimes I have had the need to programmatically get the value for a property deep into an object, but the thing is that sometimes the property is really a method, and sometimes it needs parameters!
So I came up with this solution, hope it helps devising one for your problem:
(Needs Rails' #try)
def reduce_attributes_for( object, options )
options.reduce( {} ) do |hash, ( attribute, methods )|
hash[attribute] = methods.reduce( object ) { |a, e| a.try!(:send, *e) }
hash
end
end
# Usage example
o = Object.new
attribute_map = {
# same as o.object_id
id: [:object_id],
# same as o.object_id.to_s
id_as_string: [:object_id, :to_s],
# same as o.object_id.to_s.length
id_as_string_length: [:object_id, :to_s, :length],
# I know, this one is a contrived example, but its purpose is
# to illustrate how you would call methods with parameters
# same as o.object_id.to_s.scan(/\d/)[1].to_i
second_number_from_id: [:object_id, :to_s, [:scan, /\d/], [:[],1], :to_i]
}
reduce_attributes_for( o, attribute_map )
# {:id=>47295942175460,
# :id_as_string=>"47295942175460",
# :id_as_string_length=>14,
# :second_number_from_id=>7}

Ruby access propteries with dot-notation

I'm trying to build a class that will basically be used as a data structure for storing values/nested values. I want there to be two methods, get and set, that accept a dot-notated path to recursively set or get variables.
For example:
bag = ParamBag.new
bag.get('foo.bar') # => nil
bag.set('foo.bar', 'baz')
bag.get('foo.bar') # => 'baz'
The get method could also take a default return value if the value doesn't exist:
bag.get('foo.baz', false) # => false
I could also initialize a new ParamBag with a Hash.
How would I manage this in Ruby? I've done this in other languages, but in order to set a recursive path, I would take the value by reference, but I'm not sure how I'd do it in Ruby.
This was a fun exercise but still falls under the "you probably should not do this" category.
To accomplish what you want, OpenStruct can be used with some slight modifications.
class ParamBag < OpenStruct
def method_missing(name, *args, &block)
if super.nil?
modifiable[new_ostruct_member(name)] = ParamBag.new
end
end
end
This class will let you chain however many method calls together you would like and set any number of parameters.
Tested with Ruby 2.2.1
2.2.1 :023 > p = ParamBag.new
=> #<ParamBag>
2.2.1 :024 > p.foo
=> #<ParamBag>
2.2.1 :025 > p.foo.bar
=> #<ParamBag>
2.2.1 :026 > p.foo.bar = {}
=> {}
2.2.1 :027 > p.foo.bar
=> {}
2.2.1 :028 > p.foo.bar = 'abc'
=> "abc"
Basically, take your get and set methods away and call methods like you would normally.
I do not advise you actually do this, I would instead suggest you use OpenStruct by itself to acheive some flexibility without going too crazy. If you find yourself needing to chain a ton of methods and have them never fail, maybe take a step backwards and ask "is this really the right way to approach this problem?". If the answer to that question is a resounding yes, then ParamBag might just be perfect.

Simplifying an "any of" check with or-operator in Ruby

How to simplify the following check ?...
if node[:base][:database][:adapter].empty? || node[:base][:database][:host].empty? ||
node[:base][:database][:database].empty? || node[:base][:database][:port].empty?
to something like
required_keys = { :adapter, :host, :database...etc...}
required keys - node[:base][:database] == []
This syntax is a little off, but basically subtract the keys you have from the set of required keys. If you have all the required keys in your set, the result should be empty.
I am not sure regarding the correct syntax ? . Any help would be appreciated
required_keys = [:adapter, :host, :database ]
if required_keys.any?{|x| node[:base][:database][x].empty?}
#code here
end
Or you could do also:
node[:base][:database].values_at(*required_keys).any?(&:empty?)
If you think you're going to use this functionality multiple places, you can extend the Hash class and require the extension in an initializer.
class Hash
def contains_values_for?(*keys)
keys.all? do |key|
self[key].present?
end
end
end
Now you can do:
node[:base][:database].contains_values_for?(:adapter, :host, :database, :port)

Resources