Is it possible to use the page function inside a class? - ruby

I'm writing some tests in calabash, and trying to use the page function inside a helper class.
I have my steps file
Given /^I am on my page$/ do
mypage = page(MyPage)
MyPageHelper.DoMultiActionStep()
end
And my page file
class MyPage < Calabash::ABase
def my_element_exists
element_exists(MY_ELEMENT_QUERY)
end
end
And my helper file
class MyPageHelper
def self.DoMultiActionStep
mypage = page(MyPage)
mypage.do_action_one
mypage.my_element_exists
end
end
When I run this though I get the error
undefined method 'page' for MyPageHelper:Class (NoMethodError)
The page function works fine in the steps file, but it just seems to have a problem being called from the MyPageHelper class. Is it possible to do this? Is there a using statement that I need to add in?
Thanks!

I am afraid that I don't know how to answer your question directly.
At the risk of being flamed, I recommend an alternative approach.
Option 1: If you don't need the helper class, don't bother with it.
I realize that your actual code is probably more complex, but do you need the helper here? Why not implement do_multi_action_step in the MyPage class as a method?
def do_multi_action_step
my_element_exists
my_other_method
end
Option 2: Pass an instance of MyPage
In your step you created an instance of MyPage. You should use that instance instead of creating a new one in MyPageHelper.do_multi_action_step.
def self.do_multi_action_step(my_page)
my_page.my_element_exists
my_page.my_other_method
end
Example:
# my_page_steps.rb
Given /^I am on my page$/ do
# use the await method to wait for your page
my_page = page(MyPage).await
# pass an instance instead of creating a new one
MyPageHelper.do_multi_action_step(my_page)
# or just use a method on the MyPage instance
my_page.do_multi_action_step
end
# my_page_helper.rb
class MyPageHelper
# pass the page as an object
def self.do_multi_action_step(my_page)
my_page.my_element_exists
my_page.my_other_method
end
end
# my_page.rb
require 'calabash-cucumber/ibase'
class MyPage < Calabash::IBase
# some view that is unique to this page
def trait
"view marked:'some mark'"
end
def my_element_exists
element_exists("view marked:'foo'")
end
def my_other_method
puts 'do something else'
end
# why not do this instead?
def do_multi_action_step
my_element_exists
my_other_method
end
end

Related

Re-Usable Components in Cheezy Page Object

I'm using cheezy page-object gem and cucumber.
I have page objects for an angular website and many pages contain angular ng-select element which is a dropdown. All of the ng-select elements are the same format for each page. The only thing that changes is the data and the id of the ng-select. I'd like to build some re-usable ng-select component that I can put in my page-objects as I have quite a few methods I use on the element.
class NGSelectComponent
include PageObject
def wrapper(id)
element(:element, tag_name: 'ng-select', id: id)
end
def wrapper_text_field
wrapper.text_field_element
end
def wrapper_span
wrapper.span_element(class: ['ng-value-label','ng-star-inserted'])
end
def wrapper_value
wrapper_span.text
end
def wrapper_values
wrapper.div_elements
end
end
As you can see the wrapper method is the ng-select element and it takes an id for the locator hash. This is as far as I got. I saw something like this but it looks like it only works for HTML elements.
How can I turn this into a re-usable component using page-object gem? As a sidenote I call my page objects using the on() method in my step definitions. So for example on(SomePage). I felt like that matters for however the solution turns out.
Widgets and page sections are the 2 options for re-usable components. As you will likely want to setup getters/setters for the field, widgets are the better choice.
The widget could be defined like:
class NGSelectComponent < PageObject::Elements::Element
def self.accessor_methods(accessor, name)
#
# Define a getter
#
accessor.send(:define_method, "#{name}") do
self.send("#{name}_element").value
end
#
# Define a setter. Use "#{name}=" so that the widget can be used
# in #populate_page_with
#
#
accessor.send(:define_method, "#{name}=") do |value|
self.send("#{name}_element").set(value)
end
end
def set(value)
text_field_element.set(value)
end
def value
text_field_element.value
end
def wrapper_text_field
text_field_element
end
def wrapper_span
span_element(class: ['ng-value-label','ng-star-inserted'])
end
def wrapper_value
wrapper_span.text
end
def wrapper_values
div_elements
end
PageObject.register_widget :ng_select, self, :element
end
Page objects would define the ng-select elements like any other accessor:
class TestPage
include PageObject
ng_select(:name, id: 'name')
end
Giving the page a getter/setter for the field - eg:
page = TestPage.new(browser)
page.populate_page_with(name: 'My Name')
p page.name
#=> "My Name"

Can I call a custom method on an object with this notation?

This is an example of what I am after:
def already_taken?
# Magic goes here...
end
"Charlotte".already_taken?
Would it be possible to construct a method in a way where I can call it directly on a String object, without having to modify the String class itself?
You could patch the String class with a custom module:
module MyStringPatch
def already_taken?
'yes'
end
end
String.include MyStringPatch
"Charlotte".already_taken?
If you want to add methods to any class (String in this case), without monkey-patching it, you should consider using Refinements.
module StringRefinements
refine String do
def already_taken?
puts "yes!"
end
end
end
# in another file...
using StringRefinements
"Charlotte".already_taken?
The already_taken? method will only be available in a scope that calls using StringRefinements and nowhere else.

Ruby inheritance and advised approach?

What I want is a single API which determines the class to delegate methods to based on a parameter passed through initializer. Here is a basic example:
module MyApp
class Uploader
def initialize(id)
# stuck here
# extend, etc. "include Uploader#{id}"
end
end
end
# elsewhere
module MyApp
class UploaderGoogle
def upload(file)
# provider-specific uploader
end
end
end
My desired outcome:
MyApp::Uploader('Google').upload(file)
# calls MyApp::UploaderGoogle.upload method
Please be aware the above is for demonstration purposes only. I will actually be passing an object which contains an attribute with the uploader id. Is there a better way to handle this?
Haven't tested it, but if you want to include a module:
module MyApp
class Uploader
def initialize(id)
mod = ("Uploader"+id).constantize
self.send(:include, mod)
end
end
end
If you want to extend your class with a module:
module MyApp
class Uploader
def initialize(id)
mod = ("Uploader"+id).constantize
self.class.send(:extend, mod)
end
end
end
Sounds like you want a simple subclass. UploaderGoogle < Uploader Uploader defines the basic interface and then the subclasses define the provider specific methods, calling super as necessary to perform the upload. Untested code OTTOMH below…
module MyApp
class Uploader
def initialize(id)
#id = id
end
def upload
#perform upload operation based on configuration of self. Destination, filename, whatever
end
end
class GoogleUploader < Uploader
def initialize(id)
super
#google-specific stuff
end
def upload
#final configuration/preparation
super
end
end
end
Something along those lines. To base this on a passed parameter, I'd use a case statement.
klass = case paramObject.identifierString
when 'Google'
MyApp::GoogleUploader
else
MyApp::Uploader
end
Two things: If you do this in several places, probably extract it into a method. Second, if you're getting the input from the user, you've got a lot of anti-injection work to do as well if you, for instance, create a class name directly from a provided string.

Changing ruby method context / calling a method with instance_exec

First, for the short version:
Isn't a method definition just a block? Why can't I do something like:
obj.instance_exec(&other_obj.method(:my_method))
with the goal of running some module method in the context of an instance of a separate class? The method is called, but it doesn't seem to be executed in the context of 'obj', despite the 'instance_exec' call.
The only way I can figure out how to accomplish this is to wrap all of the code of 'my_method' in a proc, then call in the following manner instead:
obj.instance_eval(&other_obj.my_method)
but I'd like to avoid encapsulating all of my module methods in procs.
Now, for the long version:
I'm attempting to create a modularized external provider system, where for any given class/method (generally controller methods,) I can call a corresponding method for a given provider (e.g. facebook).
Since there could be multiple providers, the provider methods need to be namespaced, but instead of simply including a bunch of methods like, for example, 'facebook_invitation_create', I'd like my InvitationsController instance to have a facebook member containing a create method - e.g.
class InvitationsController < ApplicationController
def create
...
# e.g. self.facebook.create
self.send(params[:provider]).create
...
end
end
Furthermore, I'd like the provider methods to not only function as if they were part of the controller itself - meaning they should have access to things like controller instance variables, params, session, etc. - but also to be (mostly) written as if they were part of the controller itself - meaning without any complex additional code as a result of being modularized.
I've created a simplified example below, in which MyClass has a greet method, which if called with a valid provider name (:facebook in this case), will call that providers greet method instead. In turn, the provider greet method accesses the message method of the including class, as if it were part of the class itself.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
class << self
def greet
proc {
"#{message} from facebook!"
}
end
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
(provider.nil? or !self.respond_to?(provider)) ? message : instance_exec(&self.send(provider).greet)
end
end
This actually accomplishes almost everything I've previously stated, but I'm hung up on the fact that my provider functions need to be encapsulated in procs. I thought maybe I could simply call instance_exec on the method instead (after removing the proc encapsulation):
instance_exec(&self.send(provider).method(:greet))
...but then it seems like the instance_exec is ignored, as I get the error:
NameError: undefined local variable or method `message' for Providers::FacebookProvider:Module
Is there any way to call instance_exec on a defined method?
(I'm open to suggestions on how to better implement this as well...)
I think this is simpler than you might expect (and I realize that my answer is 2 years after you asked)
You can use instance methods from modules and bind them to any object.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
def greet
"#{message} from facebook!"
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
if provider
provider.instance_method(:greet).bind(self).call
else
message
end
end
end
If your provider is a module, you can user instance_method to create an UnboundMethod and bind it to the current self.
This is delegation.
It's the basis for the casting gem which would work like this:
delegate(:greet, provider)
Or, if you opt-in to using method_missing from casting, your code could just look like this:
greet
But you'd need to set your delegate first:
class MyClass
include Providers
include Casting::Client
delegate_missing_methods
attr_accessor :message
def initialize(message="hello", provider=facebook)
cast_as(provider)
self.message = message
end
end
MyClass.new.greet # => "hello from facebook!"
I wrote about what delegation is and is not on my blog which is relevant to understanding DCI and what I wrote about in Clean Ruby
Maybe I'm not following along, but it seems like you are making this harder than it needs to be.
Why not implement a "dispatch" pattern in your class, where you have a hash of provider names and provider methods {:facebook=>"facebook_greet"} and then just "send" the incoming call to the correct handler via "Object#send" (http://ruby-doc.org/core-1.9.3/Object.html#method-i-send)? Send is very fast for dispatching methods, so unlike eval, you should get great performance.
Here's some code to demonstrate the way I'd solve it (assuming I am following along with what you're trying to accomplish):
module TwitterProvider
def providerInit(providers)
#providers[:twitter]="twitter_greet"
super(providers) if defined?(super)
end
def twitter_greet
"Hello Twitter User"
end
end
module FacebookProvider
def providerInit(providers)
providers[:facebook]="facebook_greet"
super(providers) if defined?(super)
end
def facebook_greet
"Hello Facebook User"
end
end
class MyClass
include FacebookProvider
include TwitterProvider
attr_accessor :message
def providerInit(providers)
super(providers) if defined?(super)
end
def initialize(message="hello")
#providers = {}
self.message = message
providerInit(#providers)
end
def greet(provider=nil)
if provider.nil? or !self.respond_to?(#providers[provider])
self.message
else
self.send(#providers[provider])
end
end
end
my_class = MyClass.new
puts my_class.greet
puts my_class.greet(:twitter)
puts my_class.greet(:facebook)
# Output:
# hello
# Hello Twitter User
# Hello Facebook User

Writing Ruby Libraries - hiding methods from outside the module

I'm writing a Ruby library which has a module with a bunch of classes inside it. Many of these classes need to be usable and modifiable by calling scripts, but I don't want (some of) the initializers to be visible/callable:
module MyLib
class Control
def initialize
# They can use this
end
def do_stuff
Helper.new('things')
end
end
class Helper
# Shouldn't be visible
def initialize(what)
#what = what
end
def shout
#what
end
end
end
c = MyLib::Control.new
h = c.do_stuff
p h.shout
# => "things"
# ^ All of this is desired
# v This is undesirable
p MyLib::Helper.new('!')
# => <MyLib::Helper #what='!'>
If it's a simple thing, then I'd also appreciate the generated RDoc not even include the .new method for the Helper class either. Any ideas?
Thanks for reading!
My original answer was completely wrong, as #Matthew pointed out. But there are other workarounds. For instance, you can assign an anonymous class to a class variable on Control, and still define methods as normal by using class_eval:
module MyLib
class Control
def initialize
end
def do_stuff
##helper.new('things')
end
##helper = Class.new
##helper.class_eval do
def initialize(what)
#what = what
end
def shout
#what
end
end
end
end
The snippet
c = MyLib::Control.new
h = c.do_stuff
p h.shout
still writes "things", but now there's no way to access ##helper except through the class variable. If someone really wants to access it my reopening the Control class or using class_eval, there's nothing to stop them, but that's just something you have to deal with in a dynamic language.
I chose to assign the anonymous class to a class variable so that it would only be created once; but if you don't care about redefining the anonymous class many times, there's no reason it couldn't be an instance variable.
Ruby has access control.

Resources