thor incorrectly called exception - ruby

class B12 < Thor
desc "write", "write data into the index"
method_option :methods, :desc => "The methods to call on each RawData", :type => :array
def write(methods)
end
end
When I call the file via
thor b12:write --methods=foo
I get
"write" was called incorrectly. Call as "thor b12:write".
Where's the problem?

You've got a couple things going on here that'll cause issues.
First, you're using methods, which is a keyword in ruby. That's going to cause all sorts of nonsense. Use something else, like my_methods.
Second, you don't need to pass my_methods into write. That creates a default option, not a named option. So you'd call thor b12:write foo if you wanted access to my_methods in that context.
This works if you call it with: thor b12:write --my_methods=foo
class B12 < Thor
desc "write", "write data into the index"
method_option :my_methods, :type => :array, :desc => "The methods to call on each RawData"
def write
puts options.my_methods
end
end

Related

How to test class methods that rely on associations with RSpec and Sinatra?

I've written some RSpec tests that successfully create objects with :let statements. However, the test environment doesn't maintain the associations that function properly everywhere else. Below is an example of a class that would turn up a NoMethodError (undefined method `money' for nil:NilClass). Money is a column in Inventory. Any thoughts?
class Inventory < ActiveRecord::Base
belongs_to :character
def self.return_money(character)
character.inventory.money
end
end
And here's a corresponding example for a spec doc:
require 'spec_helper'
describe 'Test methods' do
let(:trader) {
Character.create(
name: "Trader",
location_id: 1)
}
let(:trader_inventory) {
Inventory.create(
character_id: trader.id,
storage_capacity: 50000,
money: 20000,
markup: 1.35)
}
it "test method" do
expect(Inventory.return_money(trader)).to eq(100)
end
end
There is no reason this shouldn't work. RSpec isn't special, it's just regular Ruby code. You can confirm this by moving all of your code into a single file, something like:
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
class Inventory < ActiveRecord::Base
end
class Character < ActiveRecord::Base
has_one :inventory
end
describe 'test' do
it 'works' do
puts Character.first.inventory.money.inspect
end
end
Guesses as to what may be broken:
money is a composite field or something like that. Can you post your database schema?
Library files aren't being loaded correctly. Use puts $LOADED_FEATURES to verify that all the files that should be required have been.

Dynamically loading Thor options for a Ruby Gem

While trying to develop a simple gem to learn the process, I happened to stumble on this issue: Thor DSL takes in options to a command using the syntax: option :some_option, :type => :boolean, just prior to the method definition.
I am trying to have a dynamic set of options loaded from a file. I do this file read operation in the constructor, but it seems the option keyword for the Thor class is getting processed before the initialize method.
Any ideas to resolve this? Also it would be great if someone can explain how the option keyword works? I mean is option a method call? I don't get the design. (This is the first DSL I am trying out and am a total newbie to Ruby Gems)
#!/usr/bin/env ruby
require 'thor'
require 'yaml'
require 'tinynews'
class TinyNewsCLI < Thor
attr_reader :sources
#sources = {}
def initialize *args
super
f = File.open( "sources.yml", "r" ).read
#sources = YAML::load( f )
end
desc "list", "Lists the available news feeds."
def list
puts "List of news feed sources: "
#sources.each do |symbol, source|
puts "- #{source[:title]}"
end
end
desc "show --source SOURCE", "Show news from SOURCE feed"
option :source, :required => true
def show
if options[:source]
TinyNews.print_to_cli( options[:source].to_sym )
end
end
desc "tinynews --NEWS_SOURCE", "Show news for NEWS_SOURCE"
#sources.keys.each do |source_symbol| # ERROR: States that #sources.keys is nil
#[:hindu, :cnn, :bbc].each do |source_symbol| # I expected the above to work like this
option source_symbol, :type => :boolean
end
def news_from_option
p #sources.keys
TinyNews.print_to_cli( options.keys.last.to_sym )
end
default_task :news_from_option
end
TinyNewsCLI.start( ARGV )
After a bit of tweaking, I think I ended upon a solution that doesn't look too bad. But not sure placing code in module like that is a good practice. But anyways:
#!/usr/bin/env ruby
require 'thor'
require 'yaml'
require 'tinynews'
module TinyNews
# ***** SOLUTION *******
f = File.open( "../sources.yml", "r" ).read
SOURCES = YAML::load( f )
class TinyNewsCLI < Thor
default_task :news_from_source
desc "list", "Lists the available news feeds."
def list
puts "List of news feed sources: "
SOURCES.each do |symbol, source|
puts "- #{source[:title]}"
end
end
desc "--source NEWS_SOURCE", "Show news for NEWS_SOURCE"
option :source, :required => true, :aliases => :s
def news_from_source
TinyNews.print_to_cli( options[:source].to_sym )
end
end
end
TinyNews::TinyNewsCLI.start( ARGV )

Confusion about ways to use JSON in ruby sinatra application

I'm making a Ruby Sinatra application that uses mongomapper and most of my responses will be in the JSON form.
Confusion
Now I've come across a number of different things that have to do with JSON.
The Std-lib 1.9.3 JSON class: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/json/rdoc/JSON.html
The JSON Gem: http://flori.github.io/json/
ActiveSupport JSON http://api.rubyonrails.org/classes/ActiveSupport/JSON.html because I'm using MongoMapper which uses ActiveSupport.
What works
I'm using a single method to handle responses:
def handleResponse(data, haml_path, haml_locals)
case true
when request.accept.include?("application/json") #JSON requested
return data.to_json
when request.accept.include?("text/html") #HTML requested
return haml(haml_path.to_sym, :locals => haml_locals, :layout => !request.xhr?)
else # Unknown/unsupported type requested
return 406 # Not acceptable
end
end
the line:
return data.to_json
works when data is an instance of one of my MongoMapper model classes:
class DeviceType
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
connection Mongo::Connection.new($_DB_SERVER_CNC)
set_database_name $_DB_NAME
key :name, String, :required => true, :unique => true
timestamps!
end
I suspect in this case the to_json method comes somewhere from ActiveSupport and is further implemented in the mongomapper framework.
What doesn't work
I'm using the same method to handle errors too. The error class I'm using is one of my own:
# Superclass for all CRUD errors on a specific entity.
class EntityCrudError < StandardError
attr_reader :action # :create, :update, :read or :delete
attr_reader :model # Model class
attr_reader :entity # Entity on which the error occured, or an ID for which no entity was found.
def initialize(action, model, entity = nil)
#action = action
#model = model
#entity = entity
end
end
Of course, when calling to_json on an instance of this class, it doesn't work. Not in a way that makes perfect sense: apparantly this method is actually defined. I've no clue where it would come from. From the stack trace, apparently it is activesupport:
Unexpected error while processing request: object references itself
object references itself
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:75:in `check_for_circular_references'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `block in encode_json'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `each'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `map'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `encode_json'
But where is this method actually defined in my class?
The question
I will need to override the method in my class like this:
# Superclass for all CRUD errors on a specific entity.
class EntityCrudError < StandardError
def to_json
#fields to json
end
end
But I don't know how to proceed. Given the 3 ways mentioned at the top, what's the best option for me?
As it turned out, I didn't need to do anything special.
I had not suspected this soon enough, but the problem is this:
class EntityCrudError < StandardError
..
attr_reader :model # Model class
..
end
This field contains the effective model class:
class DeviceType
..
..
end
And this let to circular references. I now replaced this with just the class name, which will do for my purposes. Now to_json doesn't complain anymore and I'm happy too :)
I'm still wondering what's the difference between all these JSON implementations though.

Accessing command line arguments from outside of Thor class

Pretty new to Ruby and OO. Studying the text books, and all the articles that google found on Thor.
I have Thor working to capture multiple command line arguments and options. I'd like to do the rest of my programming from outside the Cli < Thor class though, and am having trouble accessing the command line arguments from outside the Cli class.
Questions:
Q1. Can the Cli < Thor class be treated like any other ruby class, or does inheriting from Thor, or the "Cli.start" command, cripple certain functionality of the Cli class versus not using Thor? Asking because I may simply not know how to access an instance variable from outside a class that doesn't use the initialize method. Thor will not let me use the initialize method to bring in the command line variables, probably because initialize is a reserved method name in ruby.
Q2. How can I access the command line argument variables a and b from outside the Thor class?
Here's my code
#!/usr/bin/env ruby
require 'thor'
class Cli < Thor
attr_reader :a, :b
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
#a = a
#b = b
puts a
puts b
end
end
Cli.start
arguments = Cli.new
puts "the first argument is #{arguments.a}"
Here's the result. Close (maybe). No errors, but arguments.a is nil.
$ ./create.rb tier a b
a
b
the first argument is
--
puts arguments.tier.a
threw the error:
./create.rb:11:in `tier': wrong number of arguments (0 for 2) (ArgumentError)
from ./create.rb:23:in `<main>'
The following works without Thor and using an initialize method and attr_reader, straight out of the text books. Can't figure out how to access the variables from a non-initialize method though.
#!/usr/bin/env ruby
class Cli
attr_reader :a, :b
def initialize(a,b)
#a = a
#b = b
end
end
arguments = Cli.new("a","b")
puts arguments.a
outupt:
$ ./create_wo_thor.rb
a
Instantiating your Cli class doesn't make much sense; that's not how Thor is designed.
You have a few options to access internal data from outside the class. If there are only a few variables that you want access to, storing them as class variables and making them available through getters (and setters, if you need them) would work:
require 'thor'
class Cli < Thor
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
##a = a
##b = b
puts a
puts b
end
def self.get_a
##a
end
def self.get_b
##b
end
end
Cli.start
puts "the first argument is #{Cli.get_a}"
This works as you hope:
$ ./thor.rb tier a b
a
b
the first argument is a
I prefer the following, using a global Hash:
require 'thor'
$args = {}
class Cli < Thor
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
$args[:a] = a
$args[:b] = b
puts a
puts b
end
end
Cli.start
puts "the first argument is #{$args[:a]}"
Last, I'd be remiss not to point out that all the command line arguments are available in the global ARGV, anyway:
require 'thor'
class Cli < Thor
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
puts a
puts b
end
end
Cli.start
puts "The first argugment is #{ARGV[1]}"
What would be best depends on how you intend to use it. If you just want raw access to the command line arguments, ARGV is the way to go. If you want to access certain pieces after Thor has done some processing for you, one of the first two might be more helpful.
Here's my code with all three options included for accessing the command line arguments from outside the Cli < Thor class. Compliments to Darshan.
#!/usr/bin/env ruby
require 'thor'
$args = {}
class Cli < Thor
attr_reader :a, :b
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
# store a and b in a global hash
$args[:a] = a
$args[:b] = b
# store a and b in class variables
##a = a
##b = b
end
# getter methods, for access of the class variables from outside the class
def self.get_a
##a
end
def self.get_b
##b
end
end
Cli.start
# three ways now to access the command line arguments from outside the Cli < Thor class
puts "the first argument, from $args[:a], is #{$args[:a]}"
puts "the second argument, from Cli.get_b, is #{Cli.get_b}"
puts "the first argument, from ARGV[1], is #{ARGV[1]}"
Results:
$ ./create.rb tier a b
the first argument, from $args[:a], is a
the second argument, from Cli.get_b, is b
the first argument, from ARGV[1], is a

Writing a DSL like Thor gem in Ruby?

I'm trying to figure out how the Thor gem creates a DSL like this (first example from their README)
class App < Thor # [1]
map "-L" => :list # [2]
desc "install APP_NAME", "install one of the available apps" # [3]
method_options :force => :boolean, :alias => :string # [4]
def install(name)
user_alias = options[:alias]
if options.force?
# do something
end
# other code
end
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
def list(search="")
# list everything
end
end
Specifically, how does it know which method to map the desc and method_options call to?
desc is pretty easy to implement, the trick is to use Module.method_added:
class DescMethods
def self.desc(m)
#last_message = m
end
def self.method_added(m)
puts "#{m} described as #{#last_message}"
end
end
any class that inherits from DescMethods will have a desc method like Thor. For each method a message will be printed with the method name and description. For example:
class Test < DescMethods
desc 'Hello world'
def test
end
end
when this class is defined the string "test described as Hello world" will be printed.

Resources