I have a definition like this
require 'httparty'
def distance_calculation
url = "https://api.distancematrix.ai/maps/api/distancematrix/json?origins=#
{#departure}&destinations=#{#destination}&key=lugtcyuuvliub;o;o"
response = HTTParty.get(url)
distance = response.parsed_response["rows"].first["elements"].first["distance"].
["text"]
end
End rspec test:
describe "#cargo" do
context "distance" do
it "returns hash with destination addresses, origin addresses & rows of datas" do
end
end
From URL parsing I get hash in which keys are destination_addresses, origin_addresses, distance and duration.
How to test by Rspec definition in which the httparty gem is used and, it does not return anything, just writes a parsed field (distance in km) to a variable.
You can stub HTTParty.get method like this working example:
require "rails_helper"
class MyClass
def distance_calculation
url = "https://api.distancematrix.ai/maps/api/distancematrix/json?origins=foo&destinations=bar&key=lugtcyuuvliub;o;o"
response = HTTParty.get(url)
distance = response.parsed_response["rows"].first["elements"].first["distance"]["text"]
end
end
RSpec.describe MyClass do
# you can write some helper methods inside your class test
def wrap_elements_body(elements)
{
rows: [{
elements: elements
}]
}
end
def build_distance_body_response(distance)
item = { distance: { text: distance } }
wrap_elements_body([item])
end
def stub_request_with(body)
body = JSON.parse(body.to_json) # just to convert symbol keys into string
response = double(parsed_response: body)
allow(HTTParty).to receive(:get).and_return(response)
end
describe "#cargo" do
context "distance" do
it "returns hash with destination addresses, origin addresses & rows of datas" do
# stubbing
expected_distance = 100.0
body_response = build_distance_body_response(expected_distance)
stub_request_with(body_response)
# running
calculated_distance = described_class.new.distance_calculation
# expectations
expect(calculated_distance).to eq(expected_distance)
end
end
end
end
You can then export those helper methods into a Helper class inside RSpec suite to use in other places.
I like to create these helper methods instead of using https://github.com/vcr/vcr because I can control more what I want and use.
Related
I am trying to store the selectors in hashes assigned to the appropriate topics which I will then scrape from a webpage. However, when I do so, I am met with an 'undefined method' error for the "css" method.
Example:
##letters_hash = {
"a" => {
uppercase: "A",
history: css('div.class_1').css('div.class_2').text,
url: "www.alphabet.com"
}
}
Is there a way to encapsulate this? Or, if I store it as a string, is there a way to remove the string and get it back to the methods?
Thank you for your time.
css('div.class_1').css('div.class_2').text is self.css('div.class_1').css('div.class_2').text and self is your Database class. It doesn't have a css method. You need to call the method on something which has a css method like a Nokogiri node.
Callbacks
If you want to store a set of methods to call on some Nokogiri node you'll get later, you make a callback using a little anonymous function called a lambda.
#letters = {
"a" => {
uppercase: "A",
history: ->(node) { node.css('div.class_1').css('div.class_2').text },
url: "www.alphabet.com"
}
}
That takes a node as an argument and calls the methods on the node.
Then later when you have a node you can call this function.
#letters_hash[letter][:history].call(node)
Objects
At this point it's getting compliated and should be encapsulated in an object.
class LetterTopic
def initialize(letter)
#letter = letter
end
def node_history(node)
node.css('div.class_1').css('div.class_2').text
end
def uppercase
#letter.upcase
end
def url
"www.alphabet.com"
end
end
letters = {
"a" => LetterTopic.new("a")
}
node = ...get a Nokogiri node...
letters[letter].node_history(node)
A Note About Class Variables
##letters_hash does not do what you think. Class variables in Ruby are shared by subclasses. If you subclass Database they will all share a single ##letters_hash variable.
class Database
##letters = {}
def self.letters
##letters
end
end
class Databasement < Database
end
Database.letters[:a] = 'database'
Databasement.letters[:a] = 'databasement'
p Database.letters # {:a=>"databasement"}
p Databasement.letters # {:a=>"databasement"}
Instead, use Class Instance Variables. Like everything else in Ruby, the Database class is an object and can have its own instance variables.
class Database
# Everything inside `class << self` works on the class object.
class << self
def letters
#letters ||= {}
end
end
end
class Databasement < Database
end
Database.letters[:a] = 'database'
Databasement.letters[:a] = 'databasement'
p Database.letters # {:a=>"database"}
p Databasement.letters # {:a=>"databasement"}
I am having trouble testing methods similar to this using minitest where objects have references to other objects:
def drive num
old_place = #current_place.name
if num == 0
road = #current_place.first_place_road
#current_place = #current_place.first_place
else
road = #current_place.second_place_road
#current_place = #current_place.second_place
end
print_drive(old_place, road)
end
I am trying to test by creating 2 mock objects, and stubbing their methods to return the other mock.
def test_drive_to_first_place
start_place = Minitest::Mock.new
end_place = Minitest::Mock.new
def start_place.name; "first place"; end
def start_place.first_place; end_place; end
def start_place.first_place_road; "road"; end
def end_place.name; "end place"; end
def end_place.first_place; nil; end
def start_place.first_place_road; nil; end
driver = Driver::new "driver", start_place
driver.drive(0)
assert_output(stdout = ......
end
I am getting this error and Im not sure how to approach it. The objects im testing do not have any property or method :end_place, it is just used as a name in testing.
1) Error:
DriverTest#test_drive_to_first_place:
NoMethodError: unmocked method :end_place, expected one of []
The correct format to set a required response would be ...
start_place.expect(:first_place, end_place)
If you define a method, you don't have access to variables defined outside the method, so your def start_place.first_place... code can't work.
I planning treating incoming data from a tcp port as if it were data from a "view". I would like to set up a number of RxRuby Observables, then depending on the data I get from the tcp port, select the appropriate Observable and publish something to it by calling the on_next method.
The following code works, but seems clumsy. The block passed to the Rx::Observable.create just sets an instance variable to the observable passed into it. It's not a huge amount of boiler plate, but something just does not seem right. Am I missing something?
require 'rx'
class GUIMessagePublisher
attr_accessor :handshake, :handshake_stream, :remote_button, :remote_button_stream
def initialize
self.handshake_stream = Rx::Observable.create { |o| self.handshake = o }
self.remote_button_stream = Rx::Observable.create { |o| self.remote_button = o }
end
def publish_handshake
handshake.on_next("hello")
end
def publish_remote_button
remote_button.on_next(nil)
end
end
publisher = GUIMessagePublisher.new
publisher.handshake_stream.subscribe { |m| puts "message = #{m}"}
publisher.remote_button_stream.subscribe { puts "remote button clicked" }
publisher.publish_handshake
publisher.publish_remote_button
After reading more about Rx::Subject, I think this would be the preferred way to handle this
require 'rx'
require 'forwardable'
class GUIMessagePublisher
extend Forwardable
attr_accessor :handshake_subject, :remote_button_subject
def_delegator :handshake_subject, :as_observable, :handshake_stream
def_delegator :remote_button_subject, :as_observable, :remote_button_stream
def initialize
self.handshake_subject = Rx::Subject.new
self.remote_button_subject = Rx::Subject.new
end
def publish_handshake
handshake_subject.on_next("hello")
end
def publish_remote_button
remote_button_subject.on_next("remote button")
end
end
publisher = GUIMessagePublisher.new
publisher.handshake_stream.subscribe { |m| puts "message = #{m}"}
publisher.remote_button_stream.subscribe { |m| puts "remote button clicked, message = #{m}" }
publisher.publish_handshake
publisher.publish_remote_button
The use of Forwardable is optional. I could have delegated via methods or even just called .as_observable on the exposed subject, but this seems right.
I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here
I have a Ruby class, and each method on it keeps indices of an array of hashes based on certain conditions.
For example (code has been edited since original posting)
module Dronestream
class Strike
class << self
...
def strike
#strike ||= all
end
def all
response['strike'] # returns an array of hashes, each individual strike
end
def in_country(country)
strike.keep_if { |strike| strike['country'] == country }
self
end
def in_town(town)
strike.keep_if { |strike| strike['town'] == town }
self
end
...
end
end
This way, you can do Dronestream::Strike.in_country('Yemen'), or Dronestream::Strike.in_town('Taizz'), and each returns an array. But I'd like to be able to do Dronestream::Strike.in_country('Yemen').in_town('Taizz'), and have it return only the strikes in that town in Yemen.
But as of now, each separate method returns an array. I know that if I have them return self, they'll have the method I need. But then they won't return an array, and I can't call, for example, first or each on them, like I could an array, which I need to do. I tried to make Strike < Array, but then, first is an instance method on Array, not a class method.
What should I do?
EDIT
Here is a part of my test suite. Following the answer below, the tests pass individually, but then fail.
describe Dronestream::Strike do
let(:strike) { Dronestream::Strike }
before :each do
VCR.insert_cassette 'strike', :record => :new_episodes
#strike = nil
end
after do
VCR.eject_cassette
end
...
# passes when run by itself and when the whole file runs together
describe '#country' do
let(:country_name) { 'Yemen' }
it 'takes a country and returns strikes from that country' do
expect(strike.in_country(country_name).first['country']).to eq(country_name)
end
end
# passes when run by itself, but fails when the whole file runs together
describe '#in_town' do
let(:town_name) { 'Wadi Abida' }
it 'returns an array of strikes for a given town' do
expect(strike.in_town(town_name).first['town'].include?(town_name)).to be_true
end
end
...
end
You can overwrite the method_missing to handle this.
Return self in your in_country or in_town method. Then when called first to it, delivery it to the all array to handle.
the code may be like this:
module Dronestream
class Strike
class << self
...
def all
...
end
def in_country(country)
all.keep_if { |strike| strike['country'] == country }
self
end
def in_town(town)
all.keep_if { |strike| strike['town'] == town }
self
end
...
def method_missing(name,*args,&block)
return all.send(name.to_sym, *args, &block) if all.respond_to? name.to_sym
super
end
end