Ruby: Trying to better understand the use of Hash#delete - ruby

I have seen a lot of examples of Ruby classes that utilize the Hash method of delete and I am not sure what the advantage of using it would be.
Example:
class Example
def initialize(default_params = {})
#foo = default_params.delete(:bar)
end
end
Any insight would be extremely helpful! Thanks!

Hash#delete is useful in the following situation:
def method(options)
if options.delete(:condition)
# Do something if options[:condition] is true
else
# Otherwise do something else
end
# Now options doesn't have the :conditions key-value pair.
another_method_that_doesnt_use_the_condition(options)
end
I'm unsure if the specific example you pulled should be using Hash#delete.

Related

Unidentified method `add` for Ruby class

This is a fake banking app:
class Bank
private def initialize
$login = Hash.new
puts "Welcome to HEIDI BANK!"
end
def add(keyVal)
keyVal.each do |key, value|
$login[key] = value
end
end
def NewUser
puts "What is the name of the user you would like to add?"
user = gets.chomp
puts "What is the password you would add?"
pass = gets.chomp
passsym = pass.to_sym
$login.add(user => passsym)
end
end
you_bank = Bank.new
you_bank.NewUser
When I attempt to run it, I get:
Welcome to HEIDI BANK!
What is the name of the user you would like to add?
12
What is the password you would add?
13
then an error is raised:
in `NewUser': undefined method `add' for {}:Hash(NoMethodError)
How would I fix this error? It seems I might have to figure out how to call Bank.add or something. Do hashes have an add function built in, like with samp_array.push for arrays?
You defined add as an instance method of Bank, but you are calling it on $login, which is an instance of Hash. Call it on the instance of Bank itself, which can be omitted:
def NewUser
...
add(user => passsym)
end
I already provided the answer in the comments but I wanted to make a longer form answer to help you out. It appears that you're a young programmer and new to Ruby so I would like to help you adopt healthy habits. Accordingly, I have refactored your code and included comments explaining the changes with links to resources that you can read to help understand why the changes were made.
These are mostly minor issues but they're important when writing any kind of production code or when writing code that others may have to read or use in the future.
# Allows the use of STDIN.noecho which will not echo text input back to the console:
# https://stackoverflow.com/a/29334534/3784008
require 'io/console'
# Use two spaces of indentation for Ruby, not tabs and not 4 spaces:
# https://github.com/rubocop-hq/ruby-style-guide#source-code-layout
class Bank
# The initialize method is not a private method:
# https://stackoverflow.com/q/1567400/3784008
def initialize
# Use single quotes for non-interpolated strings
# https://github.com/rubocop-hq/ruby-style-guide#strings
puts 'Welcome to HEIDI BANK!'
# Don't instantiate the `login` variable here; it should be lazily instantiated:
# http://blog.jayfields.com/2007/07/ruby-lazily-initialized-attributes.html
end
# Use snake_case for method names
# https://github.com/rubocop-hq/ruby-style-guide#naming
def new_user
puts 'What is the name of the user you would like to add?'
user = gets.chomp
puts 'What is the password you would add?'
# Suppress local echo of the password as it is typed
# https://stackoverflow.com/a/29334534/3784008
pass = STDIN.noecho(&:gets).chomp
# Do not call .to_sym on pass; if pass == an Integer then it will raise an exception,
# e.g., 1.to_sym => NoMethodError: undefined method `to_sym' for 1:Integer
{ user => pass }
end
end
Then you run it like you were doing before:
you_bank = Bank.new
Welcome to HEIDI BANK!
=> #<Bank:0x00007f8cc9086710>
you_bank.new_user
What is the name of the user you would like to add?
foo
What is the password you would add?
=> {"foo"=>"bar"}
Two other notes on your original code:
Don't use global variables (variable name preceded with a $). There is more explanation at this answer. If you end up needing a variable that is accessible within the instance of the class you should use an instance variable.
Write DRY code. There's no need for the add(keyVal) method because it's implemented already in Hash by merge. Try translating the issue you want to resolve into something you can search for, in this case you want to "add to hash in ruby" and the first Google result for that query is this answer that details how to do that.
I hope this helps you out.
Update
Below you asked "what is lazy instantiation?" The short answer is: don't assign variables until they need to be used. For example:
# Bad; don't do this
class Foo
def initialize
# This variable is instantiated when calling `Foo.new`,
# before it needs to be used, and so takes up memory right
# away vs. only once it's needed
#variable_used_by_example_method = 'foobar'
end
def example_method
puts #variable_used_by_example_method
end
end
# Better; okay to do this
class Foo
def initialize
end
def example_method
# This variable is lazily instantiated, that is, it is not
# instantiated until `Foo.new.example_method` is called
#variable_used_by_example_method = 'foobar'
puts #variable_used_by_example_method
end
end
For your other question about comparing usernames and passwords against what users type in, I recommend you think through the problem and if you still are unsure then post a new question. The question you've asked isn't clear enough for me to give you a good answer. Right now you're experimenting with the code to figure out how everything works, and when you're experimenting and learning it's okay to do things that don't squarely fit into object-oriented programming design patterns. But any answer I give you based on what you've told me so far would either go against those patterns, or would be too advanced. (e.g., use a database and associated models in a framework like Rails)
I wouldn't do the first because that would be bad advice, and I wouldn't do the second because you should have a firmer grasp of Ruby and programming first. So my recommendation is for you to think these through:
What is the plain-English step-by-step explanation of your overall goal?
How can that explanation be described in terms of object-oriented logic?
What code can you write to encapsulate that logic?
What are the gaps between your ability to describe the logic and your ability to write code for that logic?
Then you can begin to ask specific questions to fill in those gaps.

Rspec: Difference between allow and allow_any_instance_of

I have a simple MySQL wrapper class which will run a query and return results.
class Rsql
def initialize(db)
#client = Mysql2::Client
#db = db
end
def execute_query()
client = #client.new(#db)
client.query("select 1")
end
end
I want to test some stuff involving the results of the query, but I don't want to actually connect to a database to get the results. I tried this test, but it doesn't work:
RSpec.describe Rsql do
it "does it" do
mock_database = double
rsql = Rsql.new(mock_database)
mock_mysql_client = double
allow(mock_mysql_client).to receive(:query).and_return({"1" => 1})
allow_any_instance_of(Mysql2::Client).to receive(:new).and_return(mock_mysql_client)
expect(rsql.execute_query).to eq({"1" => 1})
end
end
Replacing allow_any_instance_of() with allow() works. I was under the impression that allow_any_instance_of() was some kind of a global "pretend this class behaves in this way across the entire program" whereas allow() is for specific instances of a class.
Can someone explain this behavior to me? I'm new to Rspec, so I apologize if this answer is blatantly obvious. I tried searching for the answer, but I couldn't come up with the right search string to find one. Maybe I don't know enough to know when I've found it.
As of RSpec 3.3 , any_instance is deprecated and not recommended to use in your tests.
From the docs:
any_instance is the old way to stub or mock any instance of a class
but carries the baggage of a global monkey patch on all classes. Note
that we generally recommend against using this feature.
You should only need to use allow(some_obj) going forward and the documentation has some great examples (see here).
Such as:
RSpec.describe "receive_messages" do
it "configures return values for the provided messages" do
dbl = double("Some Collaborator")
allow(dbl).to receive_messages(:foo => 2, :bar => 3)
expect(dbl.foo).to eq(2)
expect(dbl.bar).to eq(3)
end
end
Edit, if you really want to use any_instance, do so like this:
(Mysql2::Client).allow_any_instance.to receive(:something)
Edit2, your exact stub doesn't work because you're not stubbing an instance, you're stubbing before the object is initialized. In that case you would do allow(Mysql2::Client).to receive(:new).
this Rsql class seems a service
class Rsql
def initialize(db)
#client = Mysql2::Client
#db = db
end
def execute_query()
client = #client.new(#db)
client.query("select 1")
end
end
lets create a test for it, now we should to test this function execute_query with subject ()
and to create clients in db we can use let! like this
let!(:client1) do
FactoryBot.create(...
with this we should not use double or something
require 'rails_helper'
RSpec.describe RsqlTest do
subject(:clients) do
Rsql.execute_query()
end
context 'select' do
let!(:client1) do
FactoryBot.create(...
end
it 'should return records' do
expect(clients).to include(client1)
end
end
end

Avoid reppetitive respond_to? calling

I need to check whether a object respond to an arbitrary number of methods.
And i'm tired of doing this:
if a.respond_to?(:foo) && a.respond_to?(:bar) && a.respond_to?(:blah)
What would be a more "correct" DRY way to do this?
You can always wrap it in a helper method:
def has_methods?(obj, *methods)
methods.all?{|method| obj.respond_to? method}
end
Try this if you have nothing against monkeypatching:
class Object
def respond_to_all? *meths
meths.all? { |m| self.respond_to?(m) }
end
def respond_to_any? *meths
meths.any? { |m| self.respond_to?(m) }
end
end
p 'a'.respond_to_all? :upcase, :downcase, :capitalize
#=> true
p 'a'.respond_to_all? :upcase, :downcase, :blah
#=> false
p 'a'.respond_to_any? :upcase, :downcase, :blah
#=> true
p 'a'.respond_to_any? :upcaze, :downcaze, :blah
#=> false
UPDATE: using meths.all? and meths.any?. #MarkThomas, thanks for refreshing my mind.
UPDATE: fixing responsd typo.
Check the Active Support extension from Rails.
It has method try. It's hard to say how you can use this method because of lack of context, maybe something like this:
if a.try(:foo) && a.try(:bar) && a.try(:blah)
In order to use this method you should
require 'active_support/core_ext/object/try.rb'
Also check my version of this method the tryit:
The 'correct' way (or one of many, anyway), is Tell Don't Ask, meaning that if you're sending the object a message, you expect it to respond without complaining. This is also known as Duck Typing (if it can quack, it's a duck).
I can't give you any specific advice, because you haven't asked a specific question. If you're testing for three different methods it seems like you don't know what kind of object a is, which can be an interesting case to deal with. Post more code!

How to use getoptlong with variable options?

I have a couple Nagios scripts which inherit a common NagiosCheck class. Since every check has slightly different getopts options I thought it'd be the best to generate the available options via a NagiosCheck class method. But I'm stuck...
This is how I call the method:
class CheckFoobar < NagiosCheck
...
end
check = CheckFoobar.new
check.generate_options(
['-H', '--hostname', GetoptLong::REQUIRED_ARGUMENT],
['-P', '--port', GetoptLong::REQUIRED_ARGUMENT],
['-u', '--url', GetoptLong::REQUIRED_ARGUMENT])
The method itself:
class NagiosCheck
...
def generate_options (*args)
options = []
args.each do |arg|
options << arg
end
parser = GetoptLong.new
options.each {|arg| parser.set_options(arg)}
end
end
Then parser only stores the last item:
p parser # => #<GetoptLong:0x00000000e17dc8 #ordering=1, #canonical_names={"-u"=>"-u", "--url"=>"-u"}, #argument_flags={"-u"=>1, "--url"=>1}, #quiet=false, #status=0, #error=nil, #error_message=nil, #rest_singles="", #non_option_arguments=[]>
Do you have any advice for me how to get parser to store all arguments?
Regards,
Mike
... First question here on stackoverflow. Please bear with me if I did something wrong and let me know so that I'm able to adapt.
The generate_options method is too complex. Getoptlong.new takes an array of arrays as argument.
class NagiosCheck
def generate_options (*args)
GetoptLong.new(*args)
end
end

A ruby method to replace "="

I want to eliminate "=" sign for a particular reason. It might looks like this:
cat_that_has_name("Kelly").as(:kelly)
kelly.do_something
The "as" method here is used to generate a method "kelly" that reference my cat. Could anyone help me with this?
Any suggestions will be appreciated.
Update:
Jorg was right, I've add a simple test to demonstrate my intention:
require "test/unit"
class AsTest < Test::Unit::TestCase
def setup
#cats = ["Kelly", "Tommy"]
end
def teardown
end
def test_as
kelly1 = get_cat("Kelly")
get_cat("Kelly").as(:kelly2)
assert_equal(kelly1.object_id, kelly2.object_id)
end
private
def get_cat(name)
#cats.each do |cat|
if cat.to_s==name
return cat
end
end
return nil
end
end
It's kind of hard to figure out what you actually want. If you want some sensible answers, you will have to provide a complete code example of what you want to achieve (for example, the code you posted is missing definitions for the cat_that_has_name and so_something methods). You will also need to post a complete specification of what exactly you expect the as method to do, with usage examples and ideally also with a testsuite. After all, how do we know if our answer is correct if you haven't defined what "correct" means?
The best I could decipher from your cryptic question is something like this:
class Object
def as(name)
s = self
Object.send(:define_method, name) { s }
Object.send(:private, name)
end
end
But there is no way of knowing whether this works, because if I try to run your code example, I get a NoMethodError for cat_that_has_name and another NoMethodError for so_something.
Note also that your question is self-inconsistent: in your subject line you ask about a method to replace = (i.e. creating variables) but in your question you talk about creating methods, which would mean that you are looking for a replacement for def and not for =. Again, it would be much easier to answer correctly if there were a testsuite.

Resources