Naming convention for methods which clear instance variable - ruby

What is a good naming convention for clearing an instance variable via a method. The actual value is stored in an array and the methods filter the array to find the value.
Here are methods I already have:
class Foo
def initialize
# Array of objects
#data = []
end
# Get the variable or default value
def variable
#data.select { |o| o.attribute == 'Some Value' }.first || 'Default Value'
end
# Does the variable exist
def variable?
!!#data.select { |o| o.attribute == 'Some Value' }.first
end
# Does this make sense?
def clear_variable!
# Delete the variable
end
end
Or should it be something like delete_variable!?

If you're creating something like that, it's best to emulate the conventional method names used within the most similar structure in Ruby. In this case, it's Hash:
class Foo
def initialize
#data = [ ]
#default = 'Default Value'
end
def [](k)
found = #data.find { |o| o.attribute == k }
found ? found.value : #default
end
def has_key(k)?
!!#data.find { |o| o.attribute == k }
end
# Does this make sense?
def delete(k)
#data.reject! { |o| o.attribute == k }
end
end
Generally a method with ! in it either raises exceptions if something goes wrong, it makes a permanent modification to the state of something (e.g. in-place modification methods), or both. It's not taken to mean "reset" or "clear".

I don't see variable! as making sense in a deletion context. The ruby idiom for a method with an exclamation mark is that the method does something destructive to something else, yes, but it makes little sense in the context of a single variable.
e.g. hash.merge!, record.save! make sense, but mymodel.field! doesn't.
I'd suggest a name like remove_field or unset_field or, if you're clearing multiples, clear!.

Related

How to write a method in ruby that takes hash-style, square bracketed arguments, like mymethod[arg]?

I want to write a method in ruby to replace a current method that simply returns a hash.
So currently we have this
def some_stats
{
:foo => "bar",
:zoo => "zar"
}
end
which can be called like this:
some_stats[:foo]
I want to replace this with a method that is called with :foo, which does some more complicated things that just building a hash. However, I'd like it to be called in the same way, with the square bracket notation. EG
#this is a bit pseudo-cody but hopefully you get the idea.
def some_stats[](key)
if key.to_s =~ /something/
#do something
return something
else
#do something else
return something_else
end
end
This would still be called like some_stats[:foo]
I can't work out the syntax for defining the method name: the above, def some_stats[](key) doesn't work. I feel like this should be possible though.
You're quite close. object[sth] is just a syntax sugar for object.[](sth). So to do what you need you have to define some_stats method that returns the object which defines [] method:
class Stats
def [](key)
if key.to_s =~ /something/
#do something
return something
else
#do something else
return something_else
end
end
def some_stats
Stats.new
end
some_stats[:something_new] #=> something
some_stats[:not_new] #=> something_else
Your original method is not called with square brackets; it is called without any argument, but returns an object (in your case a Hash), which has method :[] defined and hence understands square brackets. Your method call is equivalent to
(some_stats())[:foo]
or, more explicitly, to
x = some_stats()
x[:foo]
Therefore you need - inside your method return an instance of some class which offers the [] method. Say you have written a class named MyClass which has a [] defined. You could then write a function
def some_stats
if _your condition goes here_
{ ... } # Return your Hash
else
Myclass.new
end
end
This would be used as
some_stats[:whatever]
However, to be really useful, your condition would make use of a parameter to the method, i.e.
def some_stats(param)
if param < 4000
{ ... } # Return your Hash
else
Myclass.new
end
end
And you would invoke it by
some_stats(4711)[:whatever]

Shortcut for "assign method result if method defined, otherwise default value" in Ruby

It's handy in a Ruby assignment to be able to check whether a method returns a value and, if not, provide a default:
my_var = foo() || my_default
Is there a similar shortcut to check whether the method exists in the first place? That is, a shorter version of this:
my_var = if defined? foo
foo()
else
my_default
end
Or this:
my_var = defined?(foo) ? foo() : my_default
The latter version isn't terrible, but when the method and variable names and default value get longer, it gets unwieldy:
resource_list = defined?(resource_list_override) ? resource_list_override() : [Resource.new(uri: 'http://example.org/'), Resource.new(uri: 'http://example.com/')]
I'd like to get it down to something that doesn't repeat the method name, if such a thing exists.
There's not a clean way to do it by default, but you can achieve something similar to presence.
class Object
def call_default(method_name, default)
self.respond_to?(method_name) ? self.send(method_name) : default
end
end
my_var = call_default :foo, my_default
Or, if you needed to potentially send arguments to the method:
class Object
def call_default(method_name, *args, default)
self.respond_to?(method_name) ? self.send(method_name, *args) : default
end
end
my_var = call_default :foo, 'bar', 'baz', my_default
You could write a small method to do this...
def do_or_default(method, default)
eval(method.to_s)
rescue NameError
return default
end
Pass the method to be tested as a symbol.
def return_hello
"hello"
end
# method return_hello exists...
p do_or_default(:return_hello, "goodbye")
=> "hello"
# method return_howdy doesn't exist...
p do_or_default(:return_howdy, "goodbye")
=> "goodbye"
Using eval is usually to be avoided but sometimes it can't be helped. You don't want to use this if the method to be tested is supplied by your user.

a set of strings and reopening String

In an attempt to answer this question: How can I make the set difference insensitive to case?, I was experimenting with sets and strings, trying to have a case-insensitive set of strings. But for some reason when I reopen String class, none of my custom methods are invoked when I add a string to a set. In the code below I see no output, but I expected at least one of the operators that I overloaded to be invoked. Why is this?
EDIT: If I create a custom class, say, String2, where I define a hash method, etc, these methods do get called when I add my object to a set. Why not String?
require 'set'
class String
alias :compare_orig :<=>
def <=> v
p '<=>'
downcase.compare_orig v.downcase
end
alias :eql_orig :eql?
def eql? v
p 'eql?'
eql_orig v
end
alias :hash_orig :hash
def hash
p 'hash'
downcase.hash_orig
end
end
Set.new << 'a'
Looking at the source code for Set, it uses a simple hash for storage:
def add(o)
#hash[o] = true
self
end
So it looks like what you need to do instead of opening String is open Set. I haven't tested this, but it should give you the right idea:
class MySet < Set
def add(o)
if o.is_a?(String)
#hash[o.downcase] = true
else
#hash[o] = true
end
self
end
end
Edit
As noted in the comments, this can be implemented in a much simpler way:
class MySet < Set
def add(o)
super(o.is_a?(String) ? o.downcase : o)
end
end

Name of the current object/class instance

I need to load a YAML file (I'm experimenting with SettingsLogic) and I'd like the instance to load the YAML with the same name as it. Briefly:
class MySettings < SettingsLogic
source "whatever_the_instance_is_called.yml"
# Do some other stuff here
end
basic_config = MySettings.new # loads & parses basic_config.yml
advanced_cfg = MySettings.new # loads & parses advanced_cfg.yml
...and so on...
The reason for this I don't yet know what configuration files I'll have to load, and typing:
my_config = MySettings.new("my_config.yml")
or
my_config = MySettings.new(:MyConfig)
just seems to be repeating myself.
I took a look around both Google and Stackoverflow, and the closest I came to an answer is either "Get Instance Name" or a discussion about how meaningless an instance name is! (I'm probably getting the query wrong, however.)
I have tried instance#class, and instance#name; I also tried instance#_id2ref(self).
What am I missing?!
Thanks in advance!
O.K., so with local variable assignment, there are snags, such as that assignment might occur slightly later than local variable symbol addition to the local variable list. But here is my module ConstMagicErsatz that I used to implement something similar to out-of-the box Ruby constant magic:
a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"
The advantage here is that you don't have to write ABC = Class.new( name: "ABC" ), name gets assigned 'magically'. This also works with Struct class:
Koko = Struct.new
Koko.name #=> "Koko"
but with no other classes. So here goes my ConstMagicErsatz that allows you to do
class MySettings < SettingsLogic
include ConstMagicErsatz
end
ABC = MySettings.new
ABC.name #=> "ABC"
As well as
a = MySettings.new name: "ABC"
a.name #=> "ABC"
Here it goes:
module ConstMagicErsatz
def self.included receiver
receiver.class_variable_set :##instances, Hash.new
receiver.class_variable_set :##nameless_instances, Array.new
receiver.extend ConstMagicClassMethods
end
# The receiver class will obtain #name pseudo getter method.
def name
self.class.const_magic
name_string = self.class.instances[ self ].to_s
name_string.nil? ? nil : name_string.demodulize
end
# The receiver class will obtain #name setter method
def name= ɴ
self.class.const_magic
self.class.instances[ self ] = ɴ.to_s
end
module ConstMagicClassMethods
# #new method will consume either:
# 1. any parameter named :name or :ɴ from among the named parameters,
# or,
# 2. the first parameter from among the ordered parameters,
# and invoke #new of the receiver class with the remaining arguments.
def new( *args, &block )
oo = args.extract_options!
# consume :name named argument if it was supplied
ɴς = if oo[:name] then oo.delete( :name ).to_s
elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
else nil end
# but do not consume the first ordered argument
# and call #new method of the receiver class with the remaining args:
instance = super *args, oo, &block
# having obtained the instance, attach the name to it
instances.merge!( instance => ɴς )
return instance
end
# The method will search the namespace for constants to which the objects
# of the receiver class, that are so far nameless, are assigned, and name
# them by the first such constant found. The method returns the number of
# remaining nameless instances.
def const_magic
self.nameless_instances =
class_variable_get( :##instances ).select{ |key, val| val.null? }.keys
return 0 if nameless_instances.size == 0
catch :no_nameless_instances do search_namespace_and_subspaces Object end
return nameless_instances.size
end # def const_magic
# ##instances getter and setter for the target class
def instances; const_magic; class_variable_get :##instances end
def instances= val; class_variable_set :##instances, val end
# ##nameless_instances getter for the target class
def nameless_instances; class_variable_get :##nameless_instances end
def nameless_instances= val; class_variable_set :##nameless_instances, val end
private
# Checks all the constants in some module's namespace, recursivy
def search_namespace_and_subspaces( ɱodule, occupied = [] )
occupied << ɱodule.object_id # mark the module "occupied"
# Get all the constants of ɱodule namespace (in reverse - more effic.)
const_symbols = ɱodule.constants( false ).reverse
# check contents of these constant for wanted objects
const_symbols.each do |sym|
# puts "#{ɱodule}::#{sym}" # DEBUG
# get the constant contents
obj = ɱodule.const_get( sym ) rescue nil
# is it a wanted object?
if nameless_instances.map( &:object_id ).include? obj.object_id then
class_variable_get( :##instances )[ obj ] = ɱodule.name + "::#{sym}"
nameless_instances.delete obj
# and stop working in case there are no more unnamed instances
throw :no_nameless_instances if nameless_instances.empty?
end
end
# and recursively descend into the subspaces
const_symbols.each do |sym|
obj = ɱodule.const_get sym rescue nil # get the const value
search_namespace_and_subspaces( obj, occupied ) unless
occupied.include? obj.object_id if obj.kind_of? Module
end
end
end # module ConstMagicClassMethods
end # module ConstMagicErsatz
The above code implements automatic searching of whole Ruby namespace with the aim of finding which constant refers to the given instance, whenever #name method is called.
The only constraint using constants gives you, is that you have to capitalize it. Of course, what you want would be modifying the metaclass of the object after it is already born and assigned to a constant. Since, again, there is no hook, you have to finde the occasion to do this, such as when the new object is first used for its purpose. So, having
ABC = MySettings.new
and then, when the first use of your MySettings instance occurs, before doing anything else, to patch its metaclass:
class MySettings
def do_something_useful
# before doing it
instance_name = self.name
singleton_class.class_exec { source "#{instance_name}.yml" }
end
# do other useful things
end
Shouldn't you be able to do either
File.open(File.join(File.expand_path(File.dir_name(__FILE__)), foo.class), "r")
or
require foo.class
The first one need not be that complicated necessarily. But if I'm understanding you correctly, you can just use foo.class directly in a require or file load statement.
Adjust as necessary for YAML loading, but #class returns a plain old string.
Well if you have tons of variables to instantiate, I'd personally just create a Hash to hold them, it's cleaner this way. Now to instantiate all of this, you could do a loop other all your yaml files :
my_settings = {}
[:basic_config, :advanced_cfg, :some_yaml, :some_yaml2].each do |yaml_to_parse|
my_settings[yaml_to_parse] = MySettings.new(yaml_to_parse)
end
Make sure your initialize method in MySettings deals with the symbol you give it!
Then get your variables like this :
my_settings[:advanced_cfg]
Unfortunately, Ruby has no hooks for variable assignment, but this can be worked around. The strategy outline is as follows: First, you will need to get your MySettings.new method to eval code in the caller's binding. Then, you will find the list of local variable symbols in the caller's binding by calling local_variables method there. Afterwards, you will iterate over them to find which one refers to the instance returned by super call in your custom MySettings.new method. And you will pass its symbol to source method call.

Perform method after select variable assignments in Ruby

I'm trying to DRY up some code, and I feel like Ruby's variable assignment must provide a way to simplify this. I have a class with a number of different instance variables defined. Some of these are intended to be hidden (or read-only), but many are public, with read/write access.
For all of the variables with public write-access, I want to perform a certain method after each assignment. I know that, in general, I can do this:
def foo=(new_foo)
#foo = new_foo
post_process(#foo)
end
def bar=(new_bar)
#bar = new_bar
post_process(#foo)
end
However, it seems that there should be a nice way to DRY this up, since I'm doing essentially the same thing after each assignment (ie, running the same method, and passing the newly-assigned variable as a parameter to that method). Since I have a number of such variables, it would be great to have a general-purpose solution.
Simpler solution
If you assign those variables in batch, you can do something like this:
kv_pairs = {:foo => new_foo_value,
:bar => new_bar_value}
kv_pairs.each do |k, v|
self.send(k.to_s + '=', v)
post_process(v)
end
Metaprogramming
Here's some ruby magic :-)
module PostProcessAssignments
def hooked_accessor( *symbols )
symbols.each { | symbol |
class_eval( "def #{symbol}() ##{symbol}; end" )
class_eval( "def #{symbol}=(val) ##{symbol} = val; post_process('#{symbol}', val); end" )
}
end
end
class MyClass
extend PostProcessAssignments
hooked_accessor :foo
def post_process prop, val
puts "#{prop} was set to #{val}"
end
end
mc = MyClass.new
mc.foo = 4
puts mc.foo
Outputs:
foo was set to 4
4

Resources