Turning a single file into MVC without Rails - ruby

I need to take the single file code below and separate it into a Model, View, Controller (MVC) ruby program that can run by the ruby command in the command line without using Rails (for instructions on how to run this program from irb, check out the README.md on my RubyBank Github Repo).
require_relative 'view'
class BankAccount
attr_accessor :name, :balance
def initialize(name, balance=0)
#name = name
#balance = balance
end
def show_balance(pin_access)
if pin_access == pin || pin_access == bank_manager
puts "\nYour current balance is: $#{#balance}"
else
puts pin_error_message
end
end
def withdraw(pin_access, amount)
if pin_access == pin
#balance -= amount
puts "'\nYou just withdrew $#{amount} from your account. \n\nYour remaining balance is: $#{#balance}\n\n"
else
puts pin_error_message
end
if #balance < 0
#balance += amount
return overdraft_protection
end
end
def deposit(pin_access, amount)
if pin_access == pin
#balance += amount
puts "\nYou just deposited $#{amount} into your account. \n\nYour remaining balance is: $#{#balance}"
else
puts pin_error_message
end
end
private
def pin
#pin = 1234
end
def bank_manager
#bank_manager = 4321
end
def pin_error_message
puts "Invalid PIN number. Try again."
end
def overdraft_protection
puts "\nYou have overdrafted your account. We cannot complete your withdrawl. Please deposit money before trying again. \n\nYour corrected balance is $#{#balance}"
end
end
I am looking for a good place to start or a general approach towards taking on such a task.

A simple approach would be to create three classes:
BankAccount minus text output is your Model.
All the text I/O goes into your View. Prompt the user for an action or registration. Get the model (for displaying data) from your controller or use the model directly.
Your Controller is responsible for a) reacting to user input, b) modifying the model and c) for holding state not directly related to the BankAccount (this point is discussable) like being logged in or what actions are possible from your current state. Your Controller receives all actions with user supplied data from your view.
Clean separation between View and Controller may be a bit hard in a console application. Also, there are about a million possible ways to implement this in a MVC style. Most important point: no UI-Code (puts/gets) in your model.

Related

Struggling to write code and tests for my Tube System program, TDD using Rspec and Ruby

I am writing a small program for a train system.
I have a passenger, coach, train and station class (and thus, a spec test for each).
My test for my passenger class is as such:
let (:passenger) {Passenger.new}
it "should not be touched in to a station when initialized" do
expect(passenger.touchedin?).to be false
end
it "should be able to enter coach" do
coach = Coach.new
passenger.enter(coach)
expect{coach.to receive(:enter)}
end
it "should be able to alight coach" do
coach = Coach.new
passenger.alight(coach)
expect{coach.to receive(:alight)}
end
it "should be able to touch into station" do
station = Station.new
passenger.touchin(station)
expect{station.to receive(:touchin)}
end
it "should be able to touch out of station" do
station = Station.new
passenger.touchout(station)
expect{station.to receive(:touchout)}
end
end
And my passenger class is like this (at the moment :p):
class Passenger
def initialize
#touchedin = false
end
def enter(coach)
end
def touchedin?
#touchedin
end
def alight(coach)
end
def touchin(station)
end
def touchout(station)
end
end
I am unsure how to satisfy my tests, if my tests are even correct in the first place.
Any help is really appreciated!
You've not really said how you're modeling the relationship between coaches and passengers, but one way I could think of could be as follows. I'm just putting enough for the coach/passenger relationship (so nothing about touching in as this involves the station) - and I'm using minitest syntax, but I think you can get the idea of what's happening.
class Coach
def initialize
#passengers = []
end
...
end
class Passenger
def initialize
#touched_in = false
end
def alight(coach)
coach.passengers << self.uid # or self, if you want the whole passenger object available
end
...
end
coach = Coach.new
assert_empty coach.passengers
joe = Passenger.new
refute_includes coach.passengers, joe.uid # or joe
joe.alight(coach)
assert_includes coach.passengers, joe.uid # or joe

Tracking score and rounds played using Sinatra

I have spent the last two weeks building a Sinatra app, and I have hit my first major roadblock. I have spent the last 3 days trying to find a solution, but need to ask for help.
I am building a small game using Sinatra, and I am having trouble understanding a concept.
The game tracks rounds and scores.
Here is some of my code:
#Player Class
class Player
attr_accessor :name, :guess, :round_score, :total_score
def initialize
#guess = 0
#round_score = 0
#total_score = 0
end
end
#Tracks Round
class RoundCount
attr_accessor :round
def initialize
#round = 0
end
def count
#round += 1
end
end
helpers do
#New game function. Makes set number of player classes in ##player array.
def new_game(players)
##i = RoundCount.new
##player = []
players.times do |x|
##player[x] = Player.new
##player[x].name = "Player#{x}"
end
end
#Calculates score
def player_score()
params.each do |x, y|
##player.each do |z|
if z.name == x
z.guess = y.to_i
z.round_score = (y.to_i - ##movie[##i.round].ratings['critics_score'].to_i).abs
z.total_score += (y.to_i - ##movie[##i.round].ratings['critics_score'].to_i).abs
end
end
end
end
get '/' do
slim :home
end
get '/players' do
slim :players
end
post '/numplayers' do
new_game(params[:numplayers].to_i)
slim :names
end
post '/names' do
slim :titles
end
Currently the program gives me the error "warning: class variable access from topelevel" and is affected by other users who play at the same time on seperate computers.
I need help understanding how to track players scores and the round of the game in each view, in a way that isn't affected by other players running a different game.
Any suggestions on how to avoid using class variables for round and score, while retaining a way to access them in each route and view?
Use Sessions or Cookies
Generally, keeping server-side state through class variables will make your life harder. There's also usually more than one way to do anything, but for your use case I'd recommend that you store your state in a session variable or a cookie.
You could then store your state in any serializable object, such as a Hash, Array, Struct, or OpenSruct. This will allow for concurrent users, and do away with the need for the class variables you're currently using.

Assert_equal undefined local variable LRTHW ex52

Hi I made it to the lase exercise os Learn Ruby The Hard Way, and I come at the wall...
Here is the test code:
def test_gothon_map()
assert_equal(START.go('shoot!'), generic_death)
assert_equal(START.go('dodge!'), generic_death)
room = START.go("tell a joke")
assert_equal(room, laser_weapon_armory)
end
And here is the code of the file it should test:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def ==(other)
self.name==other.name&&self.description==other.description&&self.paths==other.paths
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
generic_death = Room.new("death", "You died.")
And when I try to launch the test file I get an error:
generic_death = Room.new("death", "You died.")
I tried to set the "generic_death = Room.new("death", "You died.")" in test_gothon_map method and it worked but the problem is that description of the next object is extremely long, so my questions are:
why assertion doesn't not respond to defined object?
can it be done different way then by putting whole object to testing method, since description of the next object is extremely long...
The nature of local variable is that they are, well, local. This means that they are not available outside the scope they were defined.
That's why ruby does not know what generic_death means in your test.
You can solve this in a couple of ways:
define rooms as constants in the Room class:
class Room
# ...
GENERIC_DEATH = Room.new("death", "You died.")
LASER_WEAPON_ARMORY = Room.new(...)
end
def test_gothon_map()
assert_equal(Room::START.go('shoot!'), Room::GENERIC_DEATH)
assert_equal(Room::START.go('dodge!'), Room::GENERIC_DEATH)
room = Room::START.go("tell a joke")
assert_equal(room, Room::LASER_WEAPON_ARMORY)
end
assert the room by its name, or some other identifier:
def test_gothon_map()
assert_equal(START.go('shoot!').name, "death")
assert_equal(START.go('dodge!').name, "death")
room = START.go("tell a joke")
assert_equal(room.name, "laser weapon armory")
end

is there any account recovery gem for ruby on rails that uses SMS?

I was wondering if there was any account recovery gem for a ruby on rails password allowing the app to send the user to his sms a pin to reset their password in the event the user forgets it? Googled but didn't see anything, figured I would ask here in case my google search string was just poorly written.
ruby on rails account recovery via sms
I'm not aware of any gem, but this sounds like something that wouldn't be terribly difficult to implement. As vgoff has mentioned, there are plenty of SMS services available to you.
Something along the lines of (haven't tested this):
class SMSReset < ActiveRecord::Base
TOKEN_LENGTH = 4
EXPIRY_TIME = 15.minutes
belongs_to :user
before_create :generate_token, :set_expiry
def dispatch_sms!
MySMSProvider.send_sms(to: user.mobile_number, body: "Your SMS token is: #{token}")
end
def has_not_expired?
expires_at > Time.now
end
private
def generate_token
self[:token] = SecureRandom.hex[0..TOKEN_LENGTH - 1].downcase
end
def set_expiry
self[:expires_at] = EXPIRY_TIME.from_now
end
end
class PasswordResetController < ApplicationController
def new
end
def create
#user = User.where(email: params[:email]).first
if #user
sms_reset = #user.create_sms_reset!
sms_reset.dispatch_sms!
flash.now[:success] = "Please enter the code that was sent to your phone in the field below"
else
flash.now[:error] = "No user was found by that email address"
render :new
end
end
def validate_token
sms_reset = SMSReset.where(user_id: params[:user_id], token: params[:token])
if sms_reset.present? && sms_reset.has_not_expired?
#user = sms_reset.user
render :password_reset_form
else
flash.now[:error] = "Sorry, that code wasn't recognized"
render :new
end
end
end
You'll want to handle errors, and there's room for improvement, but hopefully the gist makes sense.
Not that I am directly aware of, but https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=sms gives some gems for SMS interaction.
That is one of the first places I look, as well as searching on github.com directly. RubyForge is another good source of information for finding gems.
https://rubyforge.org/search/?type_of_search=soft&words=sms&Search=Search

Learnstreet Ruby Lesson 11.5 Transfer state between objects

I've been stuck on this Learnstreet lesson for a day now. The exercise prompts:
Can you now implement a method called transfer! which takes two parameters, amount and other_account. The method should withdraw the specified amount from the current object and deposit it into other_account object.
The code in the editor goes as follows:
class BankAccount
attr_accessor :name, :balance, :address
def initialize(name, balance, address)
#name = name
#balance = balance
#address = address
end
def withdraw!(amount)
if #balance - amount > 0
#balance = #balance - amount
end
#balance
end
def deposit!(amount)
#balance += amount
end
# your code here
end
alices_account = BankAccount.new("Alice Cooper", 2500, "456 University Avenue")
bobs_account = BankAccount.new("Bob Ventura", 2100, "3500 Fox Street")
I know that you need to set up a method with def transfer!(amount, other_account). However I do not know what to put in the bottom after alices_account and bobs_account.
You'd call transfer! on one of the objects, passing in the other, e.g.,
bobs_account.transfer!(500, alices_account)
You're just calling a method on an instance, like "foo".size or [1, 2, 3].each etc. The only difference is that you've created the method you're calling.
I know that you need to set up a method with def transfer!(amount, other_account).
So basically you have to create BankAccount#transfer! that withdraw some money from the object that calls it and deposit the sum into the "other" BankAccount object.
The solution is pretty trivial since you have the BankAccount#withdraw! and BankAccount#deposit! already set up:
def transfer!(amount, other_account)
withdraw! amount
other_account.deposit! amount
end
However I do not know what to put in the bottom after alices_account and bobs_account.
The exercise doesn't require you to do anything with the latter. If you were supposed to do something you would need to know the amount of "money" to transfer from alices_account to bobs_account an viceversa and then go with:
# assuming x to be the amount to transfer
alices_account.transfer! x, bobs_account
or:
# assuming x to be the amount to transfer
bobs_account.transfer! x, alices_account
Ok now. I've spent an hour to complete all the 10 course before that one and this is what I discovered. At some point you get to write the last two lines of your code.
Then a weird thing happens. The code generated by the exercise contains a . To near the end which is obviously a syntax error. By removing that line and adding the method I provided above you get to pass the test.

Resources