How do you make ruby variables and methods in scope using Thor Templates? - ruby

I'm trying to use the Thor::Actions template method to generate some C++ test file templates, but erb keeps telling me that I have undefined variables and methods.
Here's the calling code:
def test (name, dir)
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
insert_into_file "src/#{dir}/test/CMakeLists.txt",
"#{dir}/test/#{name}Test ", :after => "set(Local "
end
Here's the template:
<% test_name = name + "Test" %>
#include <gtest/gtest.h>
#include "<%= dir %>/<%= name %>.h"
class <%= test_name %> : public testing::Test {
protected:
<%= test_name %> () {}
~<%= test_name %> () {}
virtual void SetUp () {}
virtual void TearDown () {}
};
// Don't forget to write your tests before you write your implementation!
TEST_F (<%= test_name %>, Sample) {
ASSERT_EQ(1 + 1, 3);
}
What do I have to do to get name and dir into scope here? I have more complex templates that I need this functionality for too.

I realize you already solved this, but I'm posting this answer in case someone else turns up looking for the solution to the question you asked (as I was).
Inside the class that #test belongs to, make an attr_accessor, then set its value in the same method that calls the template.
class MyGenerator < Thor
attr_accessor :name, :dir
def test (name, dir)
self.name = name
self.dir = dir
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
end
end
Note: that if you chain methods using #invoke, then a new instance of the class will be used for each invocation. Therefore you have to set the instance variable in the method with the template call. For example, the following wont work.
class MyGenerator < Thor
attr_accessor :name
def one (name)
self.name = name
invoke :two
end
def two (name)
# by the time we get here, this is another instance of MyGenerator, so #name is empty
template "tasks/templates/new_test_file", "src/#{name}Test.cpp"
end
end
You should put self.name = name inside #two instead
For making generators, if you inherit from Thor::Group instead, all the methods are called in order, and the attr_accessor will be set up for you with the instance variables set for each method. In my case, I had to use Invocations instead of Thor::Group because I couldn't get Thor::Group classes to be recognized as subcommands of an executable.

ERB uses ruby's binding object to retrieve the variables that you want. Every object in ruby has a binding, but access to the binding is limited to the object itself, by default. you can work around this, and pass the binding that you wish into your ERB template, by creating a module that exposes an object's binding, like this:
module GetBinding
def get_binding
binding
end
end
Then you need to extend any object that has the vars you want with this module.
something.extend GetBinding
and pass the binding of that object into erb
something.extend GetBinding
some_binding = something.get_binding
erb = ERB.new template
output = erb.result(some_binding)
for a complete example of working with ERB, see this wiki page for one of my projects: https://github.com/derickbailey/Albacore/wiki/Custom-Tasks

Related

ruby: calling a instance method without using instance

I know in ruby, when we call an instance method, we need to firstly instantiate a class object.
But when I see a open sourced code I got confused.
The code is like this:
File Message.rb
require 'json'
module Yora
module Message
def serialize(msg)
JSON.generate(msg)
end
def deserialize(raw, symbolized_key = true)
msg = JSON.parse(raw, create_additions: true)
if symbolized_key
Hash[msg.map { |k, v| [k.to_sym, v] }]
else
msg
end
end
end
end
File. Persistance.rb
require 'fileutils'
require_relative 'message'
module Yora
module Persistence
class SimpleFile
include Message
def initialize(node_id, node_address)
#node_id, #node_address = node_id, node_address
FileUtils.mkdir_p "data/#{node_id}"
#log_path = "data/#{node_id}/log.txt"
#metadata_path = "data/#{node_id}/metadata.txt"
#snapshot_path = "data/#{node_id}/snapshot.txt"
end
def read_metadata
metadata = {
current_term: 0,
voted_for: nil,
cluster: { #node_id => #node_address }
}
if File.exist?(#metadata_path)
metadata = deserialize(File.read(#metadata_path)) #<============
end
$stderr.puts "-- metadata = #{metadata}"
metadata
end
.....
You can see the line I marked with "<==="
It uses deserialize function that been defined in message class.
And from message class we can see that method is a instance method, not class method.
So why can we call it without instantiating anything like this?
thanks
Message ist an module. Your Class SimpleFile includes this module. so the module methods included in your class SimpleFile. that means, all module methods can now be used like as methods from SimpleFile
see http://ruby-doc.org/core-2.2.0/Module.html for more infos about module in ruby. it's a great feature.
It is being called on an instance. In Ruby, if you leave out the explicit receiver of the message send, an implicit receiver of self is assumed. So, deserialize is being called on an instance, namely self.
Note that this exact same phenomenon also occurs in other places in your code, much earlier (in line 1, in fact):
require 'fileutils'
require_relative 'message'
Here, you also have two method calls without an explicit receiver, which means that the implicit receiver is self.

undefined method error when accessing class page object from module

I'm trying to access class method which is defined in Module, I can call function but function has page object element which performs some operation like click, I'm getting following error:
undefined method "label_year" for Datefunctions:Class (NoMethodError)
Here's my files structure:
./lib/calender_util.rb:
module CalenderUtil
def set_date
Datefunctions.get_calender_year
end
end
class Datefunctions
include PageObject
span(:label_year, :class=> 'ui-datepicker-year')
span(:label_month, :class=> 'ui-datepicker-month')
def self.get_calender_year
return label_year
end
end
./home_page.rb:
require 'calender_helper.rb'
include CalenderUtil
def setTravelDate date
CalenderUtil.set_date
end
parts of env.rb:
require 'page-object'
require 'page-object/page_factory'
$: << File.dirname(__FILE__)+'/../../lib'
require 'calender_helper.rb'
include CalenderHelper
World PageObject::PageFactory
World CalenderHelper
In addition; I've defined include/require multiple times I'll take off once this solved.
The reason is, the methods auto-generated by PageObject, are all instance methods. You can't use it in a class method because there is no instance.
Look at the doc's example:
class LoginPage
include PageObject
text_field(:username, :id => 'username')
text_field(:password, :id => 'password')
button(:login, :id => 'login')
end
login_page.username = 'cheezy'
login_page.password = 'secret'
login_page.login
The methods are for instances.
To fix, you need to create an instance.
module CalenderUtil
def set_date
page = Datefunctions.new(args_foo)
page.label_year
end
end
The problem is that label_year is an instance method while get_calender_year is a class method. You cannot call the instance method since you have not created an instance of the class.
As Billy Chan pointed out, for your code to work, you need to create an instance of the Datefunctions class within your module. This seems a bit awkward since you would need to pass the browser instance to each method called in the CalenderUtil. To me CalenderUtil is a layer of abstraction that is not adding any value.
I think that you should:
Use modules to encapsulate controls that are used across multiple pages.
Include these modules within the page object classes that have the controls.
Call the methods from the page objects.
For your example, I would create a Datefunctions module that defines the datepicker controls.
module DateFunctions
include PageObject
span(:label_year, :class=> 'ui-datepicker-year')
span(:label_month, :class=> 'ui-datepicker-month')
end
Then for each page class that uses the datepicker control, include the module:
class MyPage
include PageObject
include DateFunctions
end
In your tests, I assume it is Cucumber but the same is true for whatever framework, use the method from the page object.
page = MyPage.new(browser)
page.label_year.should == '1/1/2013'

How to call a page-object from a class.rb at Support folder

I am using the page-object gem. Suppose i have a page-object on features/bussines/pages/booking_page.rb for a page like:
class Booking
include PageObject
span(:txtFirstName, :id => 'details_first_name')
end
...and i use a "tools" class located at features/support/tools.rb with something like:
class MyTools
def call_to_page_object
on Booking do |page|
puts page.txtFirstName
end
end
end
...but this approach fails because calling to the object from the class is not allowed:
undefined method `on' for #<Booking:0x108f5b0c8> (NoMethodError)
Pretty sure i'm missing some concept on the way to use the page-object from a class but don't realize whats the problem. Can you please give me an idea about what could be wrong here, please?
Thank you very much!
============================
Justin found the reason why the call to the class crash. The final class code results:
class MyTools
#Include this module so that the class has the 'on' method
include PageObject::PageFactory
def initialize(browser)
#Assign a browser object to #browser, which the 'on' method assumes to exist
#browser = browser
end
def getCurrentRewards
on Booking do |page|
rewards_text = page.rewards_amount
rewards_amount = rewards_text.match(/(\d+.*\d*)/)[1].to_f
puts "The current rewards amount are: #{rewards_amount}."
return rewards_amount
end
end
end
And the call to the function:
user_rewards = UserData.new(#browser).getCurrentRewards
Why it did not work me? Two main reasons:
I didn't pass the browser object to the class <== REQUIRED
I didn't include the PageObject::PageFactory in the class <== REQUIRED for the "on" method.
Thanks all!
To use the on (or on_page) method requires two things:
The method to be available, which is done by including the PageObject::PageFactory module.
Having a #browser variable (within the scope of the class) that is the browser.
So you could make your MyTools class work by doing:
class MyTools
#Include this module so that the class has the 'on' method
include PageObject::PageFactory
def initialize(browser)
#Assign a browser object to #browser, which the 'on' method assumes to exist
#browser = browser
end
def call_to_page_object
on Booking do |page|
puts page.txtFirstName
end
end
end
You would then be calling your MyTools class like:
#Assuming your Cucumber steps have the the browser stored in #browser:
MyTools.new(#browser).call_to_page_object
What are you trying to do?
Did you read Cucumber & Cheese book?
Pages should be in the features/support/pages folder. You can put other files that pages need there too.
If you want to use on method in a class, you have to add this to the class:
include PageObject
The code from MyTools class looks to me like it should be in Cucumber step file, not in a class.
Your class should use the extend keyword to access special class methods like span:
class Booking
extend PageObject
span(:txtFirstName, :id => 'details_first_name')
end
I hope this works.

Shoulda: How would I use an instance variable outside of a setup or should block?

I'm trying to do something like the following:
#special_attributes = Model.new.methods.select # a special subset
#special_attributes.each do |attribute|
context "A model with #{attribute}" do
setup do
#model = Model.new
end
should "respond to it by name" do
assert_respond_to #model, attribute
end
end
end
However, #special_attributes is out of scope when running the unit tests, leaving me with a nil object on line 2. I can't figure out where/how to define it to bring it in scope. Any thoughts?
Got it (I think). Shoulda is executing the block in the context of Shoulda::Context. In the above case, #special_attributes is an instance variable of my test class, not Shoulda::Context. To fix this, instead of using instance variables, just use local variables in the context block.
So, for example:
context "Model's" do
model = Model.new
special_attributes = model.methods.select # a special subset
special_attributes.each do |attribute|
context "attribute #{attribute}" do
setup do
#model = model
end
should "should have a special characteristic"
assert_respond_to #model, attribute
...
end
end
end
end

Ruby Mixins and Instance variables

Is there a best practice way to pass params to mixed-in methods?
The class using the mixin could set up instance variables which mixed-in methods expect, or it could pass all necessary params as arguments to mixed-in methods.
The background to this is that we have a Rails controller which does publishing of content - but other controllers and even Models need to be able to "act as publshers" so I'm factoring the controllers methods into a Module which I'll mixin as needed.
Here, for example, is code from a Rails Controller which needs to "act as publisher" and it calls a mixed-in method question_xhtml()...
def preview
#person = Person.find params[:id]
#group = Group.find params[:parent_id]
#div = Division.find params[:div_id]
#format = 'xhtml'
#current_login = current_login
xhtml = person_xhtml() # CALL TO MIXED-IN METHOD
render :layout => false
end
Ultimately question_xhtml needs all that stuff! Is this approach reasonable, or would it be better to do
def preview
person = Person.find params[:id]
group = Group.find params[:parent_id]
div = Division.find params[:div_id]
format = 'xhtml'
xhtml = person_xhtml(person, group, div, format) # CALL TO MIXED-IN METHOD
render :layout => false
end
...or something else?
I think you should be able to do:
module ActAsPublisher
def person_xhtml
do_stuff_with(#person, #group, #div, #format, #current_login)
# eg. use instance variable directly in the module
end
end
class WhateverController < Application Controller
act_as_publisher
...
end
if you used script/generate plugin act_as_publisher.

Resources