I am doing TodoList, and
want to save the list to a JSON file, but it saves only the Class name.
Here's the code:
require 'date'
require 'json'
class TodoList
attr_accessor :title
def initialize(title)
#title=title
#items=[]
end
def rename(title)
#title=title
end
def add_item(new_item, due_date)
item=Item.new(new_item)
item.set_due_date(due_date)
#items.push(item)
end
def save
if json_instance = self.to_json
puts "Saved Successfully"
end
File.open("file_json_complete.json", "w") do |f|
f.write(json_instance)
end
end
end
class Item
attr_reader :complete_status, :description, :due_date, :created_at
def initialize(description)
#description=description
#complete_status=false
#created_at=Date.today
end
end
doh=TodoList.new("doh's stuff")
# Add four new items
doh.add_item("laundry", 10)
doh.add_item("study",20)
doh.add_item("sleep", 15)
doh.add_item("Watch Movie", 5)
doh.save
The result in the file only shows the class name TodoList:0x00000000965e88.
To do this appropriately I would recommend adding the following:
class TodoList
def to_json(options={})
{title: #title, items: #items}.to_json(options)
end
end
class Item
def to_json(options={})
{description: #description, completed: #complete_status,
created: #created_at, due: #due_date}.to_json(options)
end
end
Then when you call to_json on the TodoList you will get something similar to:
"{\"title\":\"doh's stuff\",
\"items\":[
{\"description\":\"laundry\",\"complete\":false,\"created\":\"2016-03-08\",\"due\":\"2017-01-08\"},
{\"description\":\"study\",\"complete\":false,\"created\":\"2016-03-08\",\"due\":\"2017-11-08\"},
{\"description\":\"sleep\",\"complete\":false,\"created\":\"2016-03-08\",\"due\":\"2017-06-08\"},
{\"description\":\"Watch Movie\",\"complete\":false,\"created\":\"2016-03-08\",\"due\":\"2016-08-08\"}
]
}"
Which I am assuming is what you are looking for.
Basically to_json will recursively call to_json on the nested objects to create the structure this will end up defaulting to the to_s method when you have not implemented a to_json method.
You are saving the current instance of the TodoList class.
You have to save your #items variable. To do this you have to change self.to_json to #items.to_json
class TodoList
...
def save
File.open("file_json_complete.json", "w") do |f|
f.write(#items.to_json)
end
puts "Saved Successfully"
end
...
end
Related
I have two files in my directory, one which is garage.rb & another called car.rb.
car.rb file just holds a car object:
class Car
end
garage.rb file is as follows:
require 'car.rb' #Makes car class accessible in garage.rb
class Garage
def initialize
#capacity = []
end
attr_accessor :capacity
end
When I make a new instance of a car by calling car = Car.new, how do I put car object in the #capacity array my default?
Essentially, whenever I call car = Car.new, I want car to be put in the #capacity array instantly.
I understand that I could make a function like so:
def add_car_to_garage(car)
capacity << car
end
But, I want the car to start in the garage when it is created, so I don't want a method to add it to the array, I just want it to automatically start there when the instance of car is created.
Any advice would be appreciated. Thank you.
You can use the ObjectSpace module with each_object method inside your Car class to define method to keep track for each living instances, like this:
class Car
def self.all
ObjectSpace.each_object(self).to_a
end
end
require_relative 'car' # Makes car class accessible in garage.rb
class Garage
def initialize
#capacity = Car.all
end
attr_accessor :capacity
end
car1 = Car.new
car2 = Car.new
car3 = Car.new
garage = Garage.new
print garage.capacity # => [#<Car:0x0000563fa14128c8>, #<Car:0x0000563fa1412918>, #<Car:0x0000563fa1412990>]
Also note, I have used require_relative to include car.rb class which is in the same directory.
Edit for comment question and Cary Swoveland suggestion:
class Car
end
require_relative 'car' # Makes car class accessible in garage.rb
class Garage
def initialize; end
def capacity
ObjectSpace.each_object(Car).to_a
end
end
car1 = Car.new
garage = Garage.new
car2 = Car.new
print garage.capacity # => [#<Car:0x000055d7bd236a00>, #<Car:0x000055d7bd236b18>]
Also note, I removed attr_accessor :capacity, since you don't need it in this case.
The collection of instances of Car should be kept and maintained by that class and made available to other classes as needed. That can be done as follows.
class Car
#cars = []
class << self
attr_reader :cars
end
def initialize
self.class.cars << self
end
def self.remove_car(car)
cars.delete(car)
end
end
class Garage
def self.cars
Cars.cars
end
end
The creation of the class Car initializes the class instance variable #cars and creates a getter for it, Cars::cars, the latter by invoking attr_reader on Car's singleton class.
Let's create a car:
car1 = Car.new
#=> #<Car:0x00007fcf3db76fd8>
Then
Car.cars
#=> [#<Car:0x00007fcf3db76fd8>]
Garage.cars
#=> [#<Car:0x00007fcf3db76fd8>]
Create another car.
car2 = Car.new
#=> #<Car:0x00007fcf3db5c9a8>
Then
Car.cars
#=> [#<Car:0x00007fcf3db76fd8>, #<Car:0x00007fcf3db5c9a8>]
Garage.cars
#=> [#<Car:0x00007fcf3db76fd8>, #<Car:0x00007fcf3db5c9a8>]
Now remove a car.
Car.remove_car(car1)
#=> #<Car:0x00007fcf3db76fd8>
Then
Car.cars
#=> [#<Car:0x00007fcf3db5c9a8>]
Garage.cars
#=> [#<Car:0x00007fcf3db5c9a8>]
To make the class instance variable #cars available to instances of Garage add a simple instance method:
class Garage
def all_cars
self.class.cars
end
end
Garage.new.all_cars
#=> [#<Car:0x00007fcf3db5c9a8>]
In Car
class << self
attr_reader :cars
end
can alternatively be written
singleton_class.public_send(:attr_reader, :cars)
I have tried at least 5 different ways to do it, currently using the .each method.
I believe the problem lies in the print_songs method at the bottom of the code block.
The error I'm getting:
Artist #print_songs lists all of the artist's songs
Failure/Error: expect{artist.print_songs}.to output("Dirty Diana\nBillie Jean\n").to_stdout
expected block to output "Dirty Diana\nBillie Jean\n" to stdout, but output "#<Song:0x000000019757b0>\n#<Song:0x000000019756e8>\n"
Diff:
## -1,3 +1,3 ##
-Dirty Diana
-Billie Jean
+#<Song:0x000000019757b0>
+#<Song:0x000000019756e8>
The code:
class Artist
attr_accessor :name, :song
##all = []
def initialize(name)
#name = name
#songs = []
end
def add_song(song)
#songs << song
end
def songs
#songs
end
def self.all
##all
end
def save
self.class.all << self
end
def self.create_by_name(name)
artist = Artist.new(name)
end
def self.find_or_create_by_name(name)
artist_name = ##all.detect{|x| x.name == name}
if artist_name == nil
self.create_by_name(name)
else
artist_name
end
end
def print_songs
#songs.each{|song| puts song}
end
end
Song Class:
class Song
attr_accessor :name, :artist
def initialize(name)
#name = name
end
# def artist=(name)
# self.artist = Artist.new(name)
# end
def self.new_by_filename(file_name)
file_name.basename("")
end
end
Answer:
def print_songs
#songs.each{|song| puts song.name}
end
It's apparent that the song is probably just not a string, but an object of some sort. Add a to_s method to whatever object that is and puts should automatically call that, although you could, of course, also call to_s manually.
Here's the quick test I did to confirm this behaviour
irb(main):001:0> class Song
irb(main):002:1> def initialize(title)
irb(main):003:2> #title = title
irb(main):004:2> end
irb(main):005:1> end
=> :initialize
irb(main):006:0> s = Song.new "scarborough fair"
=> #<Song:0x0000000030bb78 #title="scarborough fair">
irb(main):007:0> puts s
#<Song:0x0000000030bb78>
=> nil
irb(main):008:0> class Song
irb(main):009:1> def to_s
irb(main):010:2> return #title
irb(main):011:2> end
irb(main):012:1> end
=> :to_s
irb(main):013:0> puts s
scarborough fair
=> nil
irb(main):014:0>
EDIT:
But why is this?
In short, when you create some custom object class, like Song in my example above, Ruby has no idea how to represent it as a string. The default to_s method simply outputs the class and the object ID, which is OK for debugging, but if there's some obvious way to represent the object as a string, like in the case of Song (one would expect song.to_s to return the song title), one has to override the default to_s method or include / inherit from another class/module that has a more fitting implementation of to_s.
It is working as expected.Lets look at tried.Could you post the test cases you have tried with?
a1 = Artist.create(name: 'Lana del re')
a1.add_song('Young and Beautiful')
a1.add_song('haunted')
a1.print_songs
["Young and Beautiful", "haunted"] #output assuming song as a String object
By default, the call to to_s prints the object's class name and an encoding of the object id like so: "#<Song:0x007f9fd16a0770>". That said, you need to override the to_s method on a Song class:
class Song
def initialize(title)
#title = title
end
def to_s
#title
end
end
Then you would need to modify the print_songs method accordingly on an existing Artist class:
def print_songs
puts #songs.each(&:to_s)
end
The each(&:to_s) bit basically invokes the to_s method on each Song object passed to the block, which is essentially the same as
def print_songs
puts #songs.each { |song| song.to_s }
end
This could've also been rewritten as just
def print_songs
puts #songs
end
and in this case puts would implicitly invoke the to_s method on the elements of #songs.
I am pretty new to programming and need some help/feedback on my code.
My goal is to scrape my data, which is working fine, and then display that data to my user in a numbered list. I am simply having difficulty displaying this data. I do not get any errors back my program simply skips my method altogether. Thanks in advance for any help/feedback!
class BestPlaces::Places
attr_accessor :name, :population, :places
##places = []
def self.list_places
# puts "this is inside list places"
self.scrape_places
end
def self.scrape_places
doc = Nokogiri::HTML(open("https://nomadlist.com/best-cities-to-live"))
places = doc.search("div.text h2.itemName").text
rank = doc.search("div.rank").text
places.collect{|e| e.text.strip}
puts "you are now in title"
##places << self.scrape_places
puts "#{rank}. #{places}"
end
end
end
CLI Page:
class BestPlaces::CLI
def list_places
puts "Welcome to the best places on Earth!"
puts #places = BestPlaces::Places.list_places
end
def call
list_places
menu
goodbye
end
end
There are a few things that could be addressed in this code, but let's first see a reworking:
require 'nokogiri'
require 'open-uri'
module BestPlaces
class Places
attr_accessor :name, :population, :places
def initialize
#places = []
end
def scrape_places
doc = Nokogiri::HTML(open("https://nomadlist.com/best-cities-to-live"))
places = doc.search("div.text h2.itemName")
ranks = doc.search("div.rank")
places.each{|e| #places << e.text.strip}
puts "you are now in title"
#places.each do |place|
i = #places.index(place)
puts "#{ranks[i].text}. #{place}"
end
end
end
class CLI
def list_places
puts "Welcome to the best places on Earth!"
BestPlaces::Places.scrape_places
end
def call
list_places
menu
goodbye
end
end
end
You have what looks to be an incomplete Module/Class setup. One can call the above like so:
bp = BestPlaces::Places.new
bp.scrape_places
The ##places variable was unnecessary, we can use #places instead to hold values that need to be accessed within the Places class. Also, nokogiri returns a string object when using the .text method on search results, which means you cannot iterate over them like an array. I hope this helps.
I'm developing some Ruby projects. I'm still learning some basic tenets of Ruby, but I need some help with a particular problem I've been having.
I need to assign some already-created elements with the methods associated with a class. How can I go about doing that?
Here's my example.
Let's say that I have an array of arrays
my_pets = ['Buddy the iguana', 'Coco the cat', 'Dawn the parakeet']
And I also have a class for which I've written a particular function that I need the my_pets array to access. Basically, this function loops through an array of strings and replaces the "a" with "#".
class Cool_Pets
def a_replace(array)
array.each do |string|
if string.include?("a")
string.gsub!(/a/, "#")
end
end
puts string
end
end
Is there a way to assign my_pets as part of the Cool_Pets class so that it can use the a_replace method?
Here's my desired result:
a_replace(my_pets) = ['Buddy the igu#na', 'Coco the c#t', 'D#wn the p#r#keet']
You could use Enumerable#map here:
my_pets.map{ |s| s.gsub(/a/,'#') }
#=> ["Buddy the igu#n#", "Coco the c#t", "D#wn the p#r#keet"]
Your code almost works, just remove puts array and the if statement. Then just call the function.
#Use CamelCase for class names NOT snake_case.
#Using two spaces for indentation is sensible.
class CoolPets
def a_replace(array)
array.each do |string|
string.gsub!(/a/, "#")
end
end
end
cool = CoolPets.new
my_pets = ['Buddy the iguana', 'Coco the cat', 'Dawn the parakeet']
p cool.a_replace(my_pets)
#=> ["Buddy the igu#n#", "Coco the c#t", "D#wn the p#r#keet"]
Not sure if this is what you're looking for, but check out Mixins: http://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html#S2
module CoolPet
def a_replace(array)
array.each do |string|
if string.include?("a")
string.gsub!(/a/, "#")
end
end
puts array.inspect
end
end
class MyPet
include CoolPet
end
array = ['Buddy the iguana', 'Coco the cat', 'Dawn the parakeet']
pet = MyPet.new
pet.a_replace(array) # => ["Buddy the igu#n#", "Coco the c#t", "D#wn the p#r#keet"]
I am by no means an expert, and I've run into a little snag when handling objects via a Readline input.
I have a Player class, an Inventory class, and an Item class, and I want the player to be able to type "wear brick" and have it match an Item.name within the array Inventory.contents and add that item to Player.worn. Here's what I have:
class Player
def wear(item)
#worn << item
end
end
class Inventory
attr_accessor :contents
def initialize
#contents = []
#stick = Item.new('a brown stick', 'long and pointy')
#brick = Item.new('a red brick', 'rough and heavy')
#contents << #stick
#contents << #brick
end
def list_contents
#contents.each {|item| puts item.name }
nil
end
end
class Item
attr_accessor :name, :desc
def initialize(name, desc)
#name = name
#desc = desc
end
end
player = Player.new
inv = Inventory.new
I have a Readline prompt to accept the first token of an input string as a command, here 'wear' as a case, but I'm having a hard time grasping how to match the subsequent string within a object inside an array of objects with one word.
inv.contents.inspect:
[#<Item:0x007fcf61935bb0 #name="a brown stick", #desc="long and pointy">, #<Item:0x007fcf61935b38 #name="a red brick", #desc="rough and heavy">]
How could I have "wear brick" search through the Array inv.contents to match #name="a red brick" and select that item to input into Player.wear?
#c650 : thank you. that looks to have solved it!
New Class:
class Player
def initialize
#inventory = Inventory.new
#worn = []
#input = "brick"
end
def wear(keyword)
#inventory.contents.map{|x| #worn << x and #inventory.contents.delete(x) if x.name.include?(keyword) }
end
end