Using default options when testin thor tasks - thor

I would like to test thor tasks with rspec, but when calling them from rspec I have two requirements:
I would like to have Thor Class instance available
I would like to call tasks with default options (as it will be called from command line)
I am not able to achieve both of these at the same time, consider following code:
require 'thor'
require 'thor/runner'
class App < Thor
method_option :foo , :default => "foovalue"
desc "xlist", "list"
def xlist(search="")
p options
end
end
app = App.new
app.xlist
app.invoke(:xlist)
App.start ARGV
Output is:
> ruby contrib/thor_test.rb xlist
{}
{}
{"foo"=>"foovalue"}
In the first two examples, I can call task through instance but default options are not passed to method (which makes spec unrealistic)
In the third example, I get default options but I can't set expectations on class instance, neither I can stub any methods which makes it hard to test. That is happening because class instance is created on the fly.

If you ask how you can test thor cli utility, I read through the thor specs as suggested in this SO answer. Examples here were particularly helpful and could be used directly.
Spec:
require 'my_thor'
describe MyThor do
it "should work" do
args = ["command", "--force"]
options = MyThor.start(args)
expect(options).to eq({ "force" => true })
end
end
Code:
class MyThor < Thor
desc "command", "do something"
method_option :force, :type => :boolean, :aliases => '-f'
def command
return options
end
end

I was able to do extract default options for a Thor command by:
Getting Thor's internal representation of the options list for that command
Building a Thor::Options object from that command list
Using that Thor::Options object to parse an empty array of options, which returns a hash of only the defaults.
The code is in get_default_options_for_command below. Honestly, I'm hoping there's a better way to do it, but I couldn't find one.
Once you've got a Thor object, you can replace its options to include those defaults plus whatever other options you want to add, then run it with #xlist.
I wrote this all up using your example above:
require 'thor'
def get_default_options_for_command(klass,command_name)
option_precursors = klass.all_commands[command_name].options
parser = Thor::Options.new(option_precursors)
parser.parse([])
end
class App < Thor
desc "xlist", "list"
method_option :foo , :default => "foovalue"
method_option :bar
def xlist(search="")
puts "search: #{search}"
puts "options: #{options}"
end
end
app = App.new
xlist_default_opts = get_default_options_for_command(App,'xlist')
new_opts = { :bar => 3 }
app.options = xlist_default_opts.merge(new_opts)
app.xlist('search-term')
The output is:
$ ./test.rb
search: search-term
options: {"foo"=>"foovalue", "bar"=>3}

Related

How to parse data and store it into variables using Nokogiri and Ruby

When I assign variable names such as service_names and name_array they are nil and nothing goes to the class variable ##product_names.
I used Pry to try the code without storing it into a variable and it works. It has the values I need.
I had this split up in more variables before to make cleaner code, for example:
require 'pry'
require 'rubygems'
require 'open-uri'
require 'nokogiri'
class KefotoScraper::CLI
##product_names =[]
PAGE_URL = "https://kefotos.mx/"
def call
binding.pry
puts "These are the services that Kefoto offers:"
#list_products
puts "which service would you like to select?"
#selection = gets.chomp
view_price_range
puts "Would you like to go back to the service menu? y/n"
answer = gets.chomp
if answer == "y"
call
end
end
private
def home_html
# #home_html ||=
# HTTParty.get root_path
Nokogiri::HTML(open(PAGE_URL))
end
#
# # TODO: read about ruby memoization
# def home_node
#
# #home_node ||=
# Nokogiri::HTML(PAGE_URL)
# end
def service_names
#service_names = home_html.css(".nav-link").map do
|link| link['href'].to_s.gsub(/.php/, "")
end
#service_names.each do |pr|
##product_names << pr
end
end
def list_products
i = 1
n = 0
while ##product_names.length < n
##product_names.each do |list_item|
puts "#{i} #{list_item[n]}"
i += 1
n += 1
end
end
end
def view_price_range
price_range = []
#service_links.each do |link|
if #service = link
link.css(".row").map {|price| price["p"].value}
price_range << p
end
price_range
end
def service_links
#service_links ||=
home_html.css(".nav-item").map { |link| link['href'] }
end
end
end
##product_names should contain the code that comes out of
home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "")
which later I turn back to an array.
This is what it looks like in Pry:
9] pry(#<KefotoScraper::CLI>)> home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "").split(",")
=> ["[\"foto-enmarcada\"", " \"impresion-fotografica\"", " \"photobooks\"", " \"impresion-directa-canvas\"", " \"impresion-acrilico\"", " \"fotoregalos\"]"]
[10] pry(#<KefotoScraper::CLI>)> home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "").split(",")[0]
=> "[\"foto-enmarcada\""
Nokogiri's command-line IRB is your friend. Use nokogiri "https://kefotos.mx/" at the shell to start it up:
irb(main):006:0> #doc.css('.nav-link[href]').map { |l| l['href'].sub(/\.php$/, '') }
=> ["foto-enmarcada", "impresion-fotografica", "photobooks", "impresion-directa-canvas", "impresion-acrilico", "fotoregalos"]
That tells us it's not dynamic HTML and shows how I'd retrieve those values. Since an a tag doesn't have to contain href parameters I guarded against retrieving any such tags by accident.
You've got bugs, potential bugs and bad practices. Here are some untested but likely to work ways to fix them:
Running the code results in:
uninitialized constant KefotoScraper (NameError)
In your code you have #service and #service_links which are never initialized so...?
Don't do this because it's cruel:
def home_html
Nokogiri::HTML(open(PAGE_URL))
end
Every time you call home_html you (re)open and (re)read the page from the remote site and wasting your and their CPU and network time. Instead, cache the parsed document in a variable kind of like you did in your commented-out line using HTTParty. It's much more friendly to not hit sites repeatedly and helps avoid getting banned.
Moving on:
def service_names
#service_names = home_html.css(".nav-link").map do
|link| link['href'].to_s.gsub(/.php/, "")
end
#service_names.each do |pr|
##product_names << pr
end
end
I'd use something like get_product_names and return the array like I did in Nokogiri above:
def get_product_names
get_html.css('.nav-link[href]').map { |l|
l['href'].sub(/\.php$/, '')
}
end
:
:
##product_names = get_product_names()
Here's why I'd do it another way. You used:
link['href'].to_s.gsub(/.php/, "")
to_s is redundant because link['href'] is already returning a string. Stringizing a string wastes brain cycles when rereading/debugging the code. Be kind to yourself and don't do that.
require 'nokogiri'
html = '<a href="foo">'
doc = Nokogiri::HTML(html)
doc.at('a')['href'] # => "foo"
doc.at('a')['href'].class # => String
gsub Ew. How many occurrences of the target string do you anticipate to find and replace? If only one, which is extremely likely in a URL "href", instead use sub because it's more efficient; It only runs once and moves on whereas gsub looks through the string at least one additional time to see if it needs to run again.
/.php/ doesn't mean what you think it does, and it's a very subtle bug in waiting. /.php/ means "some character followed by "php", but you most likely meant "a period followed by 'php'". This was something I used to see all the time because other programmers I worked with didn't bother to figure out what they were doing, and being the senior guy it was my job to pick their code apart and find bugs. Instead you should use /\.php/ which removes the special meaning of ., resulting in your desired pattern which is not going to trigger if it encounters "aphp" or something similar. See "Metacharacters and Escapes" and the following section on that page for more information.
On top of the above, the pattern needs to be anchored to avoid wasting more CPU. /\.php/ will cause the regular expression engine to start at the beginning of the string and walk through it until it reaches the end. As strings get longer that process gets slower, and in production code that is processing GB of data it can slow down a system markedly. Instead, using an anchor like /\.php$/ or /\.php\z/ gives the engine a hint where it should start looking and can result in big speedups. I've got some answers on SO that go into this, and the included benchmarks show how they help. See "Anchors" for more information.
That should help you but I didn't try modifying your code to see if it did. When asking questions about bugs in your code we need the minimum code necessary to reproduce the problem. That lets us help you more quickly and efficiently. Please see "ask" and the linked pages and "mcve".

How to pass an array between Ruby files

I want to pass an array from a Ruby file to another one.
I have a three files:
main.rb
company.rb
applicant.rb
Here is the code for main.rb:
require './src/company.rb'
require './src/applicant.rb'
company = Company.new('data/boundless.json')
company.find_applicants('google')
Here is the code for company.rb:
require 'json'
require_relative 'applicant.rb'
class Company
attr_accessor :jobs , :arrOfApp
def self.load_json(filepath)
file = File.read(filepath)
return JSON.parse(file)
end
def initialize(filePath)
# Load the json file and loop over the jobs to create an array of instance of `Job`
# Assign the `jobs` instance variable.
jobs=Array.new
data_hash = Company.load_json(filePath)
numberOfJobs= data_hash['jobs'].length
for i in 0 ... numberOfJobs
jobs[i]=data_hash['jobs'][i]['applicants']
# puts jobs
end
end
## TODO: Impelement this method to return applicants from all jobs with a
## tag matching this keyword
def find_applicants(keyWord)
app =Applicant.new
arrOfApp=Array.new
app.data_of_applicant(jobs)
end
end
And finally the code for applicant.rb:
require_relative 'company.rb'
class Applicant
attr_accessor :id, :name, :tags
def initialize
end
def data_of_applicant(j)
id=Array.new
name=Array.new
tags=Array.new
puts j
end
end
The program reads a JSON file to get some information from it. Whenever I try to print the value being sent to the applicant file nothing is printed.
You can't pass an array from a ruby file to another one., you only can pass data between classes and objects.
Other possibilities which may help:
constants (Defined with starting capital letter)
global variables (starting with $)
Singletons
To keep data inside the class instances (objects) you need attributes (variables starting with #).
You can find this concepts in every beginner manual of ruby (and if not, then the manual is not worth to be used)
You made another common error.
Let's check it with a small example:
class Company
attr_accessor :jobs
def initialize()
jobs='This should be assigned to my accessor jobs'
end
end
puts Company.new.jobs
The result is an empty line.
What happend? In the initialize-method you define a local variable jobs. Local means, it is only available in the method ans is lost when the method leaves.
Correct would be 1) using the instance variable:
class Company
attr_accessor :jobs
def initialize()
#jobs='This should be assigned to my accessor jobs'
end
end
or 2) using the accessor method:
class Company
attr_accessor :jobs
def initialize()
self.jobs='This should be assigned to my accessor jobs'
end
end
In both cases the puts Company.new.jobs returns the text you defined.
See also Ruby instance variable access
if i'm reading this correctly, you're asking ruby to make the calculation, but never stating that it should be printed. i believe changing the last line of your main.rb to this:
puts company.find_applicants('google')
should suffice.

What is "current_account.people.find" in Rails strong parameter example?

I am new to Rails and am currently learning strong parameters in Rails 4 and following the below example from the official documentation:
`class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end`
Question 1:
What does current_account.people.find mean inside the update method?
Question 2:
Could someone please explain the person_params method. What is "params" inside the person_params method?
current_account is a most likely a private method that returns an Account instance. current_account.people.find(params[:id]) searches the people table for a person that belongs to the current_account and has an ID of params[:id]. Object#tap is a ruby method that yields a block with the current object, and then returns that object. In this case, the Person instance is updated inside the block and the returned from tap. Finally, redirect_to is a controller method that will redirect the request to a different path. redirect_to can take many different types of arguments, including an ActiveRecord model, a string, or a symbol. Passing it an ActiveRecord model will redirect the request to the model's resource path, which is defined in routes.rb. In this case, that path will most likely be /people/:id.
The params object is a hash containing parameter names and values. For example, the request /people?name=Joe&age=34 will result in the following params object: {name: 'Joe', age: '34'}.

GAE: Writing the API: a simple PATCH method (Python)

I have a google-cloud-endpoints, in the docs, I did'nt find how to write a PATCH method.
My request:
curl -XPATCH localhost:8080/_ah/api/hellogreeting/1 -d '{"message": "Hi"}'
My method handler looks like this:
from models import Greeting
from messages import GreetingMessage
#endpoints.method(ID_RESOURCE, Greeting,`
path='hellogreeting/{id}', http_method='PATCH',
name='greetings.patch')
def greetings_patch(self, request):
request.message, request.username
greeting = Greeting.get_by_id(request.id)
greeting.message = request.message # It's ok, cuz message exists in request
greeting.username = request.username # request.username is None. Writing the IF conditions in each string(checking on empty), I think it not beatifully.
greeting.put()
return GreetingMessage(message=greeting.message, username=greeting.username)
So, now in Greeting.username field will be None. And it's wrong.
Writing the IF conditions in each string(checking on empty), I think it not beatifully.
So, what is the best way for model updating partially?
I do not think there is one in Cloud Endpoints, but you can code yours easily like the example below.
You will need to decide how you want your patch to behave, in particular when it comes to attributes that are objects : should you also apply the patch on the object attribute (in which case use recursion) or should you just replace the original object attribute with the new one like in my example.
def apply_patch(origin, patch):
for name in dir( patch ):
if not name.startswith( '__' ):
setattr(origin,name,getattr(patch,name))

How do I dynamically determine if a Model class exist in Google App Engine?

I want to be able to take a dynamically created string, say "Pigeon" and determine at runtime whether Google App Engine has a Model class defined in this project named "Pigeon". If "Pigeon" is the name of a existant model class, I would like to then get a reference to the Pigeon class so defined.
Also, I don't want to use eval at all, since the dynamic string "Pigeon" in this case, comes from outside.
You could try, although probably very, very bad practice:
def get_class_instance(nm) :
try :
return eval(nm+'()')
except :
return None
Also, to make that safer, you could give eval a locals hash: eval(nm+'()', {'Pigeon':pigeon})
I'm not sure if that would work, and it definitely has an issue: if there is a function called the value of nm, it would return that:
def Pigeon() :
return "Pigeon"
print(get_class_instance('Pigeon')) # >> 'Pigeon'
EDIT: Another way of doing it is possibly (untested), if you know the module:
(Sorry, I keep forgetting it's not obj.hasattr, its hasattr(obj)!)
import models as m
def get_class_instance(nm) :
if hasattr(m, nm) :
return getattr(m, nm)()
else : return None
EDIT 2: Yes, it does work! Woo!
Actually, looking through the source code and interweb, I found a undocumented method that seems to fit the bill.
from google.appengine.ext import db
key = "ModelObject" #This is a dynamically generated string
klass = db.class_for_kind(key)
This method will throw a descriptive exception if the class does not exist, so you should probably catch it if the key string comes from the outside.
There's two fairly easy ways to do this without relying on internal details:
Use the google.appengine.api.datastore API, like so:
from google.appengine.api import datastore
q = datastore.Query('EntityType')
if q.get(1):
print "EntityType exists!"
The other option is to use the db.Expando class:
def GetEntityClass(entity_type):
class Entity(db.Expando):
#classmethod
def kind(cls):
return entity_type
return Entity
cls = GetEntityClass('EntityType')
if cls.all().get():
print "EntityType exists!"
The latter has the advantage that you can use GetEntityClass to generate an Expando class for any entity type, and interact with it the same way you would a normal class.

Resources