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.
Related
I came across this line of ruby code. What does &. mean in this?
#object&.method
It is called the Safe Navigation Operator. Introduced in Ruby 2.3.0, it lets you call methods on objects without worrying that the object may be nil(Avoiding an undefined method for nil:NilClass error), similar to the try method in Rails.
So you can write
#person&.spouse&.name
instead of
#person.spouse.name if #person && #person.spouse
From the Docs:
my_object.my_method
This sends the my_method message to my_object. Any
object can be a receiver but depending on the method's visibility
sending a message may raise a NoMethodError.
You may use &. to designate a receiver, then my_method is not invoked
and the result is nil when the receiver is nil. In that case, the
arguments of my_method are not evaluated.
Note: Even though #Santosh gave a clear and full answer, I would like add some more background and add an important note regarding its use with non instance variables.
It is called "Safe Navigation Operator" (aka "Optional chaining operator", "Null-conditional operator", etc.). Matz seems to call it "lonely operator". It was introduced in Ruby 2.3. It sends a method to an object only if it is not nil.
Example:
# Call method `.profile` on `user` only if `user` is not `nil`
#user&.profile
# Equivalent to
unless #user.nil?
#user.profile
end
"Edge case" with local variables:
Please note, above code uses instance variables. If you want to use safe navigation operator with local variables, you will have to check that your local variables are defined first.
# `user` local variable is not defined previous
user&.profile
# This code would throw the following error:
NameError: undefined local variable or method `user' for main:Object
To fix this issue, check if your local variable is defined first or set it to nil:
# Option 1: Check the variable is defined
if defined?(user)
user&.profile
end
# Option 2: Define your local variable. Example, set it to nil
user = nil
user&.profile # Works and does not throw any errors
Method background
Rails has try method that basically does the same. It uses send method internally to call a method. Matz suggested that it is slow and this should be a built-in language feature.
Many other programming languages have similar features: Objective C, Swift, Scala, CoffeeScript, etc. However, a common syntax is ?. (question dot). But, this syntax could not be adopted by Ruby. Because ? was allowed in method names and thus, ?. symbol sequence is already a valid Ruby code. For example:
2.even?.class # => TrueClass
That's why Ruby community had to come up with different syntax. It was an active discussion and different options were considered (.?, ?, &&, etc.). Here is a list of some considerations:
u.?profile.?thumbnails
u\profile\thumbnails
u!profile!thumbnails
u ? .profile ? .thumbnails
u && .profile && .thumbnails
# And finally
u&.profile&.thumbnails
While choosing the syntax, developers looked at different edge cases and the discussion is quite useful to go through. If you want to go through all variants and nuance of the operator, please see this feature introduction discussion on official Ruby issue tracker.
Be wary! Though the safe navigation operator is convenient it can also be easy to trick yourself into changing your logic with it. I recommend avoiding the use of it in flow control. Example:
str = nil
puts "Hello" if str.nil? || str.empty?
# The above line is different than the below line
puts "Hello" if str&.empty?
In the first example, str.nil? returns true and str.empty? is never called, causing the puts statement to be executed. In the second example however, str&.empty? returns nil which is falsey, and the puts statement is never executed.
safe navigation operator (&.): tells Ruby to only call the next method if the receiver isn’t nil. Otherwise, the expression returns nil.
Practical In Action
Let’s construct a Roster object for a Sports team. The Roster will contain multiple Player objects.
class Roster
attr_accessor :players
end
class Player
attr_accessor :name, :position
def initialize(name, position)
#name = name
#position = position
end
end
With these two objects, we can create a roster for a 2-on-2 women’s basketball tournament:
moore = Player.new("Maya Moore", "Forward")
taurasi = Player.new("Diana Taurasi", "Guard")
tourney_roster1 = Roster.new
tourney_roster1.players = [moore, taurasi]
If we want to know the forward for our 2-on-2 team, we might find the name this way:
if tourney_roster1.players.first.position == "Forward"
puts "Forward: #{tourney_roster1.players.first.name}"
end
But what if our opposing roster isn’t set correctly?
tourney_roster2 = Roster.new
if tourney_roster2.players.first.position == "Forward"
puts "Forward: #{tourney_roster1.players.first.name}"
end
tourney_roster2 hasn’t yet been set with any players. The preceding code will raise a NoMethodError because tourney_roster2.players returns nil. We can add conditional statements to avoid this, but it makes our if statement verbose and unclear:
if tourney_roster2.players &&
tourney_roster2.players.first &&
tourney_roster2.players.first.position == "Forward"
Instead, we can use the safe navigation operator to avoid the NoMethodError:
if tourney_roster2.players&.first&.position == "Forward"
puts "Forward: #{tourney_roster1.players.first.name}"
end
Thus,
>> tourney_roster2.players&.first == nil
#=> true
>> tourney_roster2.players&.first&.position == nil
#=> true
Some legitimate use cases: The safe navigation operator comes in handy when working with multiple objects, as shown here, and when chaining methods together.
it used for nil check, such as in kotlin and swift
For example;
with Object -> Swift and Kotlin
model = car?.model
this model can be nil(Swift) or null(Kotlin) if we have not defined the model value in car class.
we use that ampersand instead of question mark in ruby
model = car&.model
if use car.model without ampersand and if model is nil the system cannot continue running.
Here's a short-read (3 mins) I found on this - it is pretty good.
To add to the above, it acts like the try! method in Rails, not the try method.
Because it will raise a NoMethodError exception if the receiver is not nil and does not implement the tried method.
Example taken from the above article:
account = Account.new(owner: Object.new)
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try(:owner).try(:address)
# => nil
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
For all of those who came here from Typescript, it's the same as the ? operator
what does mean object&.an_attribute in ruby on rails?
I am new in Ruby on rails and I saw this kind of code but I don't understand it:
In Ruby, like in most mainstream programming languages, user code cannot modify the fundamental workings of the programming languages, nor can it change the programming language's syntax.
Since Ruby on Rails is just Ruby code, it should be immediately obvious that this cannot possibly have anything to do with Ruby on Rails.
Therefore, we need to look at Ruby for an explanation, not Ruby on Rails.
The safe navigation operator or safe navigator is specified in language/safe_navigator_spec.rb of the ruby/spec, in particular here:
context "when context is nil" do
it "always returns nil" do
eval("nil&.unknown").should == nil
eval("[][10]&.unknown").should == nil
end
it "can be chained" do
eval("nil&.one&.two&.three").should == nil
end
it "doesn't evaluate arguments" do
obj = Object.new
obj.should_not_receive(:m)
eval("nil&.unknown(obj.m) { obj.m }")
end
end
It is documented in the Calling Methods section of the Ruby Syntax documentation:
&., called “safe navigation operator”, allows to skip method call when receiver is nil. It returns nil and doesn't evaluate method's arguments if the call is skipped.
In node.js you can write:
var lib = require('lib');
but in Ruby the require function simply runs the code in the file and true is returned.
Currently, I'm using a very dirty solution:
main.rb:
$stuff = []
require './file1.rb'
require './file2.rb'
# and so on
file1.rb:
$stuff << something
and so on.
How can I eliminate the use of a global variable?
eg:
main.rb:
$stuff = []
$stuff << cool_require './file1.rb'
# etc
file1.rb:
exports.what = something
One of the biggest errors when working with a language, is trying to make the language working like a different one.
Ruby is not NodeJs, there are features built-in into each language that are unique to the language and cannot be reproduced easily.
In other words, there is no way to implement the NodeJS require behavior in Ruby because in Ruby there is no notion of export. When you require a file, every method/class included in the required file are made available to the scope.
In Ruby there are objects and method visibility. The way you have to make a method visible or not is to declare it as public or private/protected.
Well, first consider that Ruby is not Node.js. As Simone Carletti said, there are some features that are unique to each language. Sometimes it's good to take from other language but sometimes it's bad.
There are few things that you must keep in mind:
meth is method invocation, to pass method you use method method: method(:meth) or package it into module/class
you can use class/module by assigning it to some 2nd variable:
class A;
def self.aa; puts 'aa'; end;
end;
New_a = A;
New_a.aa # aa;
eval is dangerous method(you can evaluate unknown code)
Method:
Here is one way you can do. It is not idiot-proof tough. It is not 100% safe(eval). :
file1.rb:
Module.new do
def self.meth1
42
end
def self.meth2
'meth2'
end
end
This file contain module with 2 methods.
I am using Module.new because it returns object that you want. You can assign it later into variable/constant.
I am using self.meth* so you don't have to include but run instantly it like this: module_name.meth1()
req.rb:
def cool_require name
eval(File.read name)
end
Some_variable = cool_require('req.rb')
puts Some_variable.meth1 # 42
puts Some_variable.meth2 # meth2
cool_require reads filename(argument name) and evaluate it(it is just like you would type it in irb/pry)
Some_variable is constant. It won't disappear that easily.
2 last line is how it works. As fair I remember, that's how node.js' require works.
As part of an online Ruby tutorial, I must create a text-based game. One requirement is that I use require to pull in another file. I've done that as well as include the module that holds a method. However, I cannot produce the result I want. Here's my file with the module:
module Inventory
def Inventory.inventory(item)
items = Array.new
if item == "show"
items.inspect
else
items << item
end
end
end
I want the parameter (item) to be added to the array items as a string that can be inspected when I pass the "show" argument to it.
So for example, I want to add a 'bat' to the inventory so I call Inventory.inventory("bat"). Later I'd like to add other things. But when I call Inventory.inventory("show") it doesn't show anything.
I've spent days looking through many other tutorials and hundreds of questions here but still can't get it work. I'm probably not understanding something really fundamental so please be gracious to me as I'm still learning.
Is it the way I'm adding to an array? The way I'm trying to get it to show? Or do I not understand how to use methods and arguments?
you can Dylan's answer if you want to go with instance approach or you can use class variables.
The problem with your code is that you initialize items local variable every time you call inventory.
Here is a version that will persist items in a class variable:
module Inventory
def Inventory.inventory(item)
##items ||= Array.new
if item == "show"
##items.inspect
else
##items << item
end
end
end
Inventory.inventory 1
Inventory.inventory 2
p Inventory.inventory 'show'
this is producing
"[1, 2]"
This would make a lot more sense as a class. This way, you can store the items in an instance variable that will persist during multiple calls to add, show, etc. You can of course put this class into a separate file and still include it.
class Inventory
def initialize
#items = []
end
def add(item)
#items << item
end
def show
#items.inspect
end
end
# To use the class:
inventory = Inventory.new
inventory.add('bat')
inventory.show
# => ["bat"]
The issue is that your items array is being recreated every time this method is called, so there is no persistence between method calls for what is passed into the array. Dylan Markow's answer shows how you can use instance variables to persist values between method calls.
Let me preface by stating I'm a "new" programmer - an IT guy trying his hand at his first "real" problem after working through various tutorials.
So - here is what I'm trying to do. I'm watching a directory for a .csv file - it will be in this format: 999999_888_filename.csv
I want to return each part of the "_" filename as a variable to pass on to another program/script for some other task. I have come up w/ the following code:
require 'rubygems'
require 'fssm'
class Watcher
def start
monitor = FSSM::Monitor.new(:directories => true)
monitor.path('/data/testing/uploads') do |path|
path.update do |base, relative, ftype|
output(relative)
end
path.create do |base, relative, ftype|
output(relative)
end
path.delete { |base, relative, ftype| puts "DELETED #{relative} (#{ftype})" }
end
monitor.run
end
def output(relative)
puts "#{relative} added"
values = relative.split('_',)
sitenum = values[0]
numrecs = values[1]
filename = values[2]
puts sitenum
end
end
My first "puts" gives me the full filename (it's just there to show me the script is working), and the second puts returns the 'sitenum'. I want to be able to access this "outside" of this output method. I have this file (named watcher.rb) in a libs/ folder and I have a second file in the project root called 'monitor.rb' which contains simply:
require './lib/watcher'
watcher = Watcher.new
watcher.start
And I can't figure out how to access my 'sitenum', 'numrecs' and 'filename' from this file. I'm not sure if it needs to be a variable, instance variable or what. I've played around w/ attr_accessible and other things, and nothing works. I decided to ask here since I've been spinning my wheels for a couple of things, and I'm starting to confuse myself by searching on my own.
Thanks in advance for any help or advice you may have.
At the top of the Watcher class, you're going to want to define three attr_accessor declarations, which give the behavior you want. (attr_reader if you're only reading, attr_writer if you're only writing, attr_accessor if both.)
class Watcher
attr_accessor :sitenum, :numrecs, :filename
...
# later on, use # for class variables
...
#sitenum = 5
...
end
Now you should have no problem with watcher.sitenum etc. Here's an example.
EDIT: Some typos.
In addition to Jordan Scales' answer, these variable should initialized
class Watcher
attr_accessor :sitenum, :numrecs, :filename
def initialize
#sitenum = 'default value'
#numrecs = 'default value'
#filename = 'default value'
end
...
end
Otherwise you'll get uninformative value nil
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.