Assign Class Methods to an already established element? - arrays

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"]

Related

Ruby Array Search Method Not Working As Intended

I am having trouble making the search method that i wrote in my script to work. Here is the relevant code from the script:
$records = []
def xml_extractor(document)
document.xpath("//record").each do |record|
$records << {"id" => record.xpath('id').text,
"first_name" => record.xpath('first_name').text,
"last_name" => record.xpath('last_name').text,
"email" => record.xpath('email').text,
"gender" => record.xpath('gender').text,
"ip_address" => record.xpath('ip_address').text,
"send_date" => record.xpath('send_date').text,
"email_body" => record.xpath('email_body').text,
"email_title" => record.xpath('email_title').text}
puts record
end
puts "\nAll records loaded!"
end
def search_by_ip(ip)
record_to_return = $records.select {|k| k["ip_address"] == ip.to_s}
puts JSON.pretty_generate(record_to_return)
end
Basically my xml_extractor method works fine and it stores everything into the array using nokogiri. The xml file that is being implemented has a thousand records each having its own first_name, last_name etc. But the problem is when i try to implement the search_by_ip method on the array a "null" value is returned, when what the method should really be doing is returning the entire record that belongs to that specific ip address. Also, i realised that every time i implement the xml_extractor method, i.e. when an xml document is parsed in into the array, the contents arent really saved in rather they are only displayed for while the loop is going. Which might be why I get a "null" for my search methods. Let me know what you guys think though.
I wrote an example of how to use OO to obtain what you want.
I don't have your document so I simplified your document to a 2 dimensional array
In the method read switch the comment to work with your xml
Each method can be chained and does only what is required
They can all be tested separatly (here by p'ting them)
class Xml_extractor
attr_reader :document, :records
def initialize document
#document = document
#records = []
end
def read
# #document.xpath("//record").each do |record|
#document.each do |record|
#records << {id: record[0], ip_address: record[1]}
end
self # return self so that you can chain another method
end
def search_by_ip(ip)
#return first of an array if found, nil otherwise
# attention to use a hash key here to search, not a string
#records.select {|k| k[:ip_address] == ip.to_s}.first
end
end
document = [[0, "192.168.0.1"], [1, "192.168.0.2"]]
p Xml_extractor.new(document).read.records
# [{:id=>0, :ip_address=>"192.168.0.1"}, {:id=>1, :ip_address=>"192.168.0.2"}]
p Xml_extractor.new(document).read.search_by_ip("192.168.0.2")
# [{:id=>1, :ip_address=>"192.168.0.2"}]
p Xml_extractor.new(document).read.search_by_ip("192.168.0.2").to_json
# "[{\"id\":1,\"ip_address\":\"192.168.0.2\"}]"
In ruby your method will return the last line. If you want your method to return data, you need to return it on the last line. puts doesn't return anything.
Try to change like this:
def search_by_ip(ip)
record_to_return = $records.select {|k| k["ip_address"] == ip.to_s}
puts JSON.pretty_generate(record_to_return)
record_to_return
end

Ruby: List all items in an array

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.

Can I control what an array of custom objects looks like in Ruby?

I have a custom class called Thing, and an array of Thing objects, like this:
class Thing
attr_accessor :name
def initialize(name)
#name = name
end
def to_s
#name
end
end
a = []
a << Thing.new("Fred")
a << Thing.new("George")
a << Thing.new("Steve")
a
When I look at the array in irb, I want it to look like this:
[Fred, George, Steve]
rather than this (the object information):
[#, #, #]
In other words, I'd like to be able to see to to_s value of each element in the array when I look at the array with irb. Is there a way to do this?
You might want to override #inspect to get a human-readable version of the object:
class Thing
attr_accessor :name
def initialize(name)
#name = name
end
def inspect
#name
end
end
a = []
a << Thing.new("Fred")
a << Thing.new("George")
a << Thing.new("Steve")
a
=> [Fred, George, Steve]
For what it's worth, irb isn't typically the interface for your code, so it's probably best not to cater too much to how your objects are represented in that narrow context.
You could override inspect, as Adam Sheehan has suggested. This will work with any instance of Thing, which could be desirable or undesirable depending on the rest of your application.
Another option if you only want this representation with this particular collection of objects is to define a custom collection:
class Things < Array
def inspect
"[#{ map(&:name).join(', ') }]"
end
end
a = Things.new
a << Thing.new("Fred")
a << Thing.new("George")
a << Thing.new("Steve")
a
You can also convert your Array into an instance of Things:
a = []
a = Things.new(a)
This is effectively a Decorator.

How to iterate and scrape data in Ruby?

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.

How to subclass an Array in Ruby?

I'm trying to subclass Array to implement a map method that returns instances of my Record class. I'm trying to create a sort of "lazy" array that only instantiates objects as they are needed to try and avoid allocating too many Ruby objects at once. I'm hoping to make better use of the garbage collector by only instantiating an object on each iteration.
class LazyArray < Array
def initialize(results)
#results = results
end
def map(&block)
record = Record.new(#results[i]) # how to get each item from #results for each iteration?
# how do I pass the record instance to the block for each iteration?
end
end
simple_array = [{name: 'foo'}, {name: 'bar'}]
lazy_array_instance = LazyArray.new(simple_array)
expect(lazy_array_instance).to be_an Array
expect(lazy_array_instance).to respond_to :map
lazy_array_instance.map do |record|
expect(record).to be_a Record
end
How can I subclass Array so that I can return an instance of my Record class in each iteration?
From what I know, you shouldn't have to do anything like this at all. Using .lazy you can perform lazy evaluation of arrays:
simple_array_of_results.lazy.map do |record|
# do something with Record instance
end
Now, you've got some odd situation where you're doing something like -
SomeOperation(simple_array_of_results)
and either you want SomeOperation to do it's thing lazily, or you want the output to be something lazy -
lazily_transformed_array_of_results = SomeOperation(simple_array_of_results)
page_of_results = lazily_transformed_array_of_results.take(10)
If that sounds right... I'd expect it to be as simple as:
SomeOperation(simple_array_of_results.lazy)
Does that work? array.lazy returns an object that responds to map, after all...
Edit:
...after reading your question again, it seems like what you actually want is something like:
SomeOperation(simple_array_of_results.lazy.collect{|r| SomeTransform(r)})
SomeTransform is whatever you're thinking of that takes that initial data and uses it to create your objects ("as needed" becoming "one at a time"). SomeOperation is whatever it is that needs to be passed something that responds to map.
So you have an array of simple attributes or some such and you want to instantiate an object before calling the map block. Sort of pre-processing on a value-by-value basis.
class Record
attr_accessor :name
def initialize(params={})
self.name = params[:name]
end
end
require 'delegate'
class MapEnhanced < SimpleDelegator
def map(&block)
#delegate_ds_obj.map do |attributes|
object = Record.new(attributes)
block.call(object)
end
end
end
array = MapEnhanced.new([{name: 'Joe'}, {name: 'Pete'}])
array.map {|record| record.name }
=> ["Joe" "Pete"]
An alternative (which will allow you to keep object.is_a? Array)
class MapEnhanced < Array
alias_method :old_map, :map
def map(&block)
old_map do |attributes|
object = Record.new(attributes)
block.call(object)
end
end
end

Resources