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.
Related
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.
I have a Volt Framework Task that checks and stores information on a directory, e.g.
class DirectoryHelperTask < Volt::Task
def list_contents()
contents = []
Dir.glob("/path/to/files").each do |f|
contents << f
end
return contents
end
end
I would like to call this from a different task, e.g.
class DirectoryRearrangerTask < Volt::Task
dir_contents = DirectoryHelperTask.list_contents()
end
The code above (DirectoryRearranger) throws an error, as does a promise call
DirectoryHelperTask.list_contents().then do |r|
dir_conents = r
end.fail do |e|
puts "Error: #{e}"
end
Could not find a way to call a task from another task in the Volt Framework documentation.
Thanks a lot!
From what I gather, tasks are meant to be run on the server side and then called on the client side, hence the use of the promise object. The promise object comes from OpalRb, so trying to call it from MRI won't work. If you have a "task" that will only be used on the server side, then it doesn't really fit with Volt's concept of a task.
Your first approach to the problem actually does work, except that DirectoryRearrangerTask can't inherit from Volt::Task.
directory_helper_task.rb
require_relative "directory_rearranger_task"
class DirectoryHelperTask < Volt::Task
def list_contents
contents = []
Dir.glob("*").each do |file|
contents << file
end
DirectoryRearrangerTask.rearrange(contents)
contents
end
end
directory_rearranger_task.rb
class DirectoryRearrangerTask
def self.rearrange(contents)
contents.reverse!
end
end
Here is a GitHub repo with my solution to this problem.
You can call tasks from the client or server, but keep in mind that you call instance methods on the class. (So they get treated like singletons) And all methods return a Promise. I think your issue here is that your doing dir_contents = DirectoryHelperTask.list_contents() inside of the class. While you could do this in ruby, I'm not sure its what you want.
Also, where you do dir_contents = r, unless dir_contents was defined before the block, its going to get defined just in the block.
I'm trying to mimic ActiveRecord with a simple set of ruby objects for running raw sql queries. Below is a spike I've been experimenting with:
module Runable
def run
return self::Results.new
end
end
module Query
class Results
def initialize
#results = Object.find_by_sql()
end
def to_a
#code
end
end
end
module Scored
extend Runable
include Query
QUERY = 'a raw sql query string'
end
module Unseen
extend Runable
include Query
QUERY = 'a different raw sql query string'
end
What I want to be able to do is create simple Modules for each type of raw sql query I'm going to run, put them into a file like Scored or Unseen above and call .run on them to get back a results object. So like this:
Scored.run #=> #<Scored::Results:0x0000000000>
Unseen.run #=> #<Unseen::Results:0x0000000000>
but instead I get this...
Scored.run #=> #<Query::Results:0x0000000000>
Unseen.run #=> #<Query::Results:0x0000000000>
I've been doing ruby and rails for over a year but I'm just beginning to get into more advanced ruby usage. This is my first big step into using modules and mixins.
The issue, as far as I can tell, is that module class methods have self scoped to the module they're defined in. So I get Query::Results because the initialize method for Results is defined in the Query module. That make sense?
Thank you for the help!
Update 5/30 16:45
Basically, I want to wrap a handful of raw SQL statements into modules like this:
module ScoredUsers
include Queryable
QUERY="SELECT * FROM users ..."
end
and interact with the queries like this:
r = ScoredUsers.run #=> ScoredUsers::Results
r.ids
r.load_objects
REDIS.zadd user:5:cache, r.to_a
I want to keep everything in modules and classes, the ruby way (I think?) so when I want to create a new query object I can simple use the boilerplate module like Scored above.
The reason why you are getting such a results is that class Results is created just once. When the module is included new constant is created within including class (Scored::Results), but it is pointing to same memory space as constant Query::Results.
What you need is that you have to create a new class for each class this module is being included in. This is perfect opportunity to use included method:
module Query
def self.included(mod)
results = Class.new do
def initialize
#results = Object.find_by_sql()
end
def to_a
#code
end
end
mod.const_set('Results', results)
end
end
Now of course we are left with the question - do we really need to do this? This depends on how you are planning to use those classes.
this is pretty crazy, and I feel really stupid asking this. But I have a basic code in Ruby and it's comprised of
user input assigned to value
input extracted to array by line
iterations over each line extracting specific text and specific numbers
if /else statements
if text includes specific words then you apply math
if text includes specific words you don't apply math
i also have a running total that i assign outside the loop and add to inside the loop
this all works fine, but the project requires class structure, how do i take a code basically
simple with if else statements basic loop (for each do |x|) and basic variable = true, or variable = input * 52/ 300
where do i start making this into class-based structure with OOP?
I'll give you a basic outline to get your started, but I won't do your work for you. :)
First: define a class to hold your input.
class MyClass
def initialize(data)
#data = data
...put your initialization code here, this gets run when you call MyClass.new()
end
attr_accessor :data
...
end
Second: define a "container" class to hold multiple instances of class.
class MyContainer
def initialize(record)
#records << record
end
...
end
Third: for each line received, create a new instance of your class
mydata = MyClass.new(input)
Forth: store the new instance in your container class.
MyContainer.new(mydata)
Now, you can do things like create an add method in MyContainer which will iterate though all the classes it's holding in #records and produce a sum.
I have an erb template which generates the config file for an httpd.
It's important that a particular location is written last (it is a catch all)
At the moment the code looks like
cluster.apps.each do |app|
# Render config
end
I'd like to overload the each method on the apps object to guarantee order. What's the best place to start looking for how to do this?
If you want to overload it, you can do something like
class Cluster
#..code
def each_application
return unless block_given? #ensure a block was given
a = #apps.shift #Implement this to grab the element you want
#apps.each{|x| yield x}
yield a #yield the element that you want last
end
end
So you can now do:
cluster.each_application do |app|
#Render config
end
And with the current implementation above, it will yield all the elements(except the first one) in a row. The last yielded item is the first one that was shifted off.