Calling a Volt Framework Task method from another Task - voltrb

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.

Related

How to stub class instantiated inside tested class in rspec

I have problem stubbing external api, following is the example
require 'rspec'
require 'google/apis/storage_v1'
module Google
class Storage
def upload file
puts '#' * 90
puts "File #{file} is uploaded to google cloud"
end
end
end
class UploadWorker
include Sidekiq::Worker
def perform
Google::Storage.new.upload 'test.txt'
end
end
RSpec.describe UploadWorker do
it 'uploads to google cloud' do
google_cloud_instance = double(Google::Storage, insert_object: nil)
expect(google_cloud_instance).to receive(:upload)
worker = UploadWorker.new
worker.perform
end
end
I'm trying to stub Google::Storage class. This class is instantiated inside the object being tested. How can I verify the message expectation on this instance?
When I run above example, I get following output, and it seems logical, my double is not used by tested object
(Double Google::Storage).upload(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
I'm new to Rspec and having hard time with this, any help will be appreciated.
Thanks!
Reaching for DI is always a good idea (https://stackoverflow.com/a/51401376/299774) but there are sometimes reasons you can't so it, so here's another way to stub it without changing the "production" code.
1. expect_any_instance_of
it 'uploads to google cloud' do
expect_any_instance_of(Google::Storage).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
In case you just want to test that the method calls the method on any such objects.
2. bit more elaborated setup
In case you want to control or set up more expectations, you can do this
it 'uploads to google cloud' do
the_double = instance_double(Google::Storage)
expect(Google::Storage).to receive(:new).and_return(the_double)
# + optional `.with` in case you wanna assert stuff passed to the constructor
expect(the_double).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
Again - Dependency Injection is clearer, and you should aim for it. This is presented as another possibility.
I would consider reaching for dependency injection, such as:
class UploadWorker
def initialize(dependencies = {})
#storage = dependencies.fetch(:storage) { Google::Storage }
end
def perform
#storage.new.upload 'test.txt'
end
end
Then in the spec you can inject a double:
storage = double
expect(storage).to receive(...) # expection
worker = UploadWorker.new(storage: storage)
worker.perform
If using the initializer is not an option then you could use getter/setter method to inject the dependency:
def storage=(new_storage)
#storage = new_storage
end
def storage
#storage ||= Google::Storage
end
and in the specs:
storage = double
worker.storage = storage

Sinatra Multiple Parallel Requests Variable Behaviour

I am fairly new to ruby and would like to understand how class instance variables behave in case of multiple parallel requests.
I have a method inside my controller class which is called everytime for each request for a specific operation (create in this case)
class DeployProvision
def self.create(data)
raise "Input JSON not received." unless data
# $logger.info input_data.inspect
failure = false
response_result = ""
response_status = "200"
#validator = SchemaValidate.new
validation = #validator.validate_create_workflow(data.to_json)
end
end
This method is called as (DeployProvision.create(data))
I am a little confused on how #validator class instance variable behaves when multiple requests come. Is it shared among multiple requests. Is it a good idea to declare this as class instance variable instead of a local variable ?
I am working on an existing code base and would like to understand the intent of creating #validator as a class instance variable instead of local variable.
You can write ultra-simple script like this:
require 'sinatra'
class Foo
def self.bar
#test = Time.now
puts #test
end
end
get '/' do
Foo.bar
end
and you'll see it does nothing, because with every call, you're creating new instance of Time(SchemaValidate in your code).
If you used memoization and had something like #validator ||= SchemaValidate.new you would have one instance of SchemaValidate stored between requests.
I don't think that'd change anything in terms of performance and I don't have idea why would anyone do something like that.
You can have some fun with ultra-simple scripts with sinatra to test how it behaves.
Good luck with this code!

How to return the receiver instance's self from should_receive block

I'd like to have instance methods of a class return self, and be init with another class instance self.
However I'm struggling to see how to spec this succintly:
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
end
When running this spec, this ensures that the next method is called on true instead of the Api instance, as expected, that is the return value of the block. Example:
class Cli
def eg
api = Api.new(self)
api.blowup # undefined method for true
end
end
I'd really like the block to return the Api instance self without invoking another call to Api.new(...) in the spec, the example below does this and to my mind a non-rspec reader would wonder why the spec passes when clearly Api.new(...) has been called more than once.
Can anyone suggest how best to do this?
Current solution:
This reads like ::Api.new(...) is called thrice: once to create api, once to create cli, once to create start. Yet the spec of one call passes. I understand why and that this is correct, so not a bug. However I'd like a spec that a reader not familiar with rspec could scan and not have the impression that Api.new has been called more than once. Also note that ...once.and_return(api){...} does not work, the block needs to return api in order to pass.
let(:cli){ ::Cli.start(['install']) }
let(:start){ ::Cli.start(['install']) }
it 'is the API' do
api = ::Api.new(cli)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
api
end
start
end
You can save the original method (new) in a local variable and then use it to return the new api from within the block:
original_method = ::Api.method(:new)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
original_method.call(arg)
end
This will run the expectation, checking that the argument is an instance of ::Cli, and then return the value from the original method (i.e. the api).

Ruby: Include a dynamic module name

I have a situation in my Rails application where I need to include arbitrary modules depending on the current runtime state. The module provides custom application code that is only needed when certain conditions are true. Basically, I'm pulling the name of a company from the current context and using that as the filename for the module and its definition:
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
include self.class.const_get(self.user.company.subdomain.capitalize.to_sym)
self.custom_add_url
end
My test module looks like this:
module Companyx
def custom_add_url
puts "Calling custom_add_url"
end
end
Now in the console, this actually works fine. I can pull a user and include the module like so:
[1] pry(main)> c = Card.find_by_personal_url("username")
[2] pry(main)> include c.class.const_get(c.user.company.subdomain.capitalize)=> Object
[3] pry(main)> c.custom_add_url
Calling custom_add_url
If I try to run the include line from my model, I get
NoMethodError: undefined method `include' for #<Card:0x007f91f9094fb0>
Can anyone suggest why the include statement would work on the console, but not in my model code?
I'm doing a similar thing. I found this answer useful:
How to convert a string to a constant in Ruby?
Turns out I was looking for the constantize method. This is the line I'm using in my code:
include "ModuleName::#{var.attr}".constantize
Edit:
So ack, I ran into various problems with actually using that line myself. Partially because I was trying to call it inside a method in a class. But since I'm only calling one method in the class (which calls/runs everything else) the final working version I have now is
"ModuleName::#{var.attr}".constantize.new.methodname
Obviously methodname is an instance method, so you could get rid of the new if yours is a class method.
Include is a method on a class.
If you want to call it inside a model, you need to execute the code in the context of its singleton class.
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
myself = self
class_eval do
include self.const_get(myself.user.company.subdomain.capitalize.to_sym)
end
self.custom_add_url
EDIT:
class << self doesn't accept a block; class_eval does, hence it preserves the state of local variables. I've modified my solution to use it.

ruby alternative to class << thing

I want to rewrite several methods of HighLine to customise my console and at the moment my code looks like this:
cmd = ask("#{#prompt_label} #{#prompt_separator} ", #tab_completion_candidates) do |q|
q.readline = true
# rewriting the Question class to make it do what we want
class << q
HERE I WRITE MY CODE
end
end
I would like to be able to separate my changes from my main console file, so let's say i have a class Console::Question that contains all the changes I want to do in HighLine::Console, I'd like to be able to do something like that:
Console::Question << q
end
But unfortunately that doesn't work :)
Any solution?
Thanks for your time.
If you put your changes in a module rather than a class then you can then do
q.extend(YourModule)
e.g. to override valid_answer?
module QuestionCustomisations
def valid_answer?
# your code here
end
end
q.extend(QuestionCustomisations)
This will apply your changes in just the object instance which is passed to the block.

Resources