Undefined method for class - Sinatra - ruby

It seems I don't quite understand initializing or using a class within another class.
I have a Sinatra app and have created a class to handle fetching data from an api
# path: ./lib/api/bikes/bike_check
class BikeCheck
def self.check_frame_number(argument)
# logic here
end
end
BikeCheck.new
I then have another class that needs to consume/use the check_frame_number method
require 'slack-ruby-bot'
# Class that calls BikeCheck api
require './lib/api/bikes/bike_check'
class BikeCommands < SlackRubyBot::Bot
match /^Is this bike stolen (?<frame_number>\w*)\?$/ do |client, data, match|
check_frame_number(match[:frame_number])
client.say(channel: data.channel, text: #message)
end
end
BikeCommands.run
When check_frame_number is called I get a undefined method error. What I would like to know is what basic thing am I not doing/understanding, I thought by requiring the file which has the class it would be available to use.

No, you can not require a method defined in class - methods defined in class only available to class, class instances and within the inheritance.
Mixing method only possible with including modules.
To solve you issue you could either do
class BikeCommands < SlackRubyBot::Bot
match /^Is this bike stolen (?<frame_number>\w*)\?$/ do |client, data, match|
BikeCheck.check_frame_number(match[:frame_number]) # <===========
client.say(channel: data.channel, text: #message)
end
end
or write a module with the method and include/extend in class, you want that method to be available in.

Related

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'

ruby, no method error

I am receiving the following error when running my below ruby script:
s3parse.rb:12:in `block in <class:AccountLog>': undefined method `extract_account_id' for AccountLog:Class (NoMethodError)
I dont think it should be a class method, is there a reason its not taking my method into account?
class AccountLog
attr_accessor :bytes, :account_id, :date
def extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
end
def extract_account_id will define an instance method.
In the way you call it, you need a class method instead.
Define it like this:
def self.extract_account_id(line)
or, as you already have an AccountLog instance, use it to call extract_account_id:
account_log.account_id = account_log.extract_account_id(line)
Please note that with second way you do not need to alter method definition, just call extract_account_id via account_log instance.
And i guess you would want to put s3log = File... outside class definition.
Or use a constant instead: S3log = ...
Then you'll can access it as AccountLog::S3log
Is there any reason you don't think it should be a class method? You are using it in the context of a class method and that's why it it's saying no such method for class AccountLog.
If you name your method as self.extract_account_id(line) I'm sure it will work.
From what you are trying to do I think this is what you are looking for?
class AccountLog
attr_accessor :bytes, :account_id, :date
def self.extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
While you could take the class method approach, there seems to be a little more going on.
You should put the extraction logic in a method in itself rather than let it hangout in your class. Then outside of the class, have an instance of AccountLog where you can call on the methods for log and account id extraction. At that point you can do something with those values.
Class method or not is a detail we can explore after the class is a bit more clean I think.

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.

Monkey patch class methods

I'm trying to do some monkey patching in ActiveShipping UPS class .
I need to add a class level method (starting with .self), so here it's what I'm trying to do:
module ActiveMerchant
module Shipping
class UPS < Carrier
def self.process_request(receiver, sender, packages, options = {})
# some code
end
def regular_method
"foobar"
end
end
end
end
Unfortunately when I'm trying to use it:
ActiveMerchant::Shipping::UPS.process_request(receiver etc)
I get an error:
NoMethodError: undefined method `process_request' for ActiveMerchant::Shipping::UPS:Class
from (irb):6
from C:/Ruby19/bin/irb.bat:19:in `<main>'
There is no class method named process_request in original class.
In original UPS class provided in gem there is one static method defined self.retry_safe = true
and I can use it without errors.
I can also use regular_method after creating instance of UPS class.
More details provided:
I'm working with Rails 2.3 ( :-( ) and Ruby 1.9.2. I have no influce on environment.
Monkey patched code is under plugins/my_plugin/lib/active_shipping/ext/carriers/ups.rb
In /active_shipping I have file named extensions.rb in which i have:
require 'active_shipping'
require_relative 'ext/carriers'
require_relative 'ext/carriers/ups'
It deals with loading everything properly (I suppose basing on regular_method beheaviour from first chunk of code in my question).
I try to invoke process_request in one of my Controllers. This part is little tricky, beacuse i'm using sth like this:
MyModel.courier_service.process_request(parameters)
where courier_service, in this case holds the ActiveMerchant::Shipping::UPS class.
I'm still a newbie in Ruby and don't know what sort of details i should provide.
Maybe you want to do it in another way
File patch_classes.rb:
module ActiveMerchantExpand
module Shipping
module ClassMethods
def self.process_request(receiver, sender, packages, options = {})
# some code
end
end
module InstanceMethods
def regular_method
"foobar"
end
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
end
Then you have to load your class "ActiveMerchant::Shipping::UPS"
and after that you can attach your methods to your class via
Rails.configuration.to_prepare do
require_dependency [[file for ActiveMerchant::Shipping::UPS]]
require 'patch_classes' )
ActiveMerchant::Shipping::UPS.send(:include, ::ActiveMerchantExpand::Shipping)
end
This is from rails plugin writing, i hope this helps.
regards tingel2k
Do you explicitly require file with your monkey patch? If you just put it under your app or lib path without requiring, it wouldn't load because constant ActiveMerchant::Shipping::UPS is defined in gem and it doesn't trigger dependency resolution mechanism.

Rails Module issue - undefined method 'validates_email' for User:class

I have a class called User, that needs to include a module.
The class looks like this:
require 'sequel'
require 'modules/validations'
class User < Sequel(..)
many_to_one :country
includes ::Validations
validates_email(:email)
end
The module is defined in a subfolder called modules. It has been added to the $LOAD_PATH and Ruby is no complaining about the loading. The module looks like this:
module Validations
def validates_email(attr, options = {})
email = super.email
end
end
The error I am getting is :
undefined method 'validates_email' for User:class
What am I missing to make this work properly?
If you want to use modules to define class methods you should use the extends method.
class User < Sequel(..)
extend ::Validations
validates_email(:email)
...
end
I suggest reading this article: http://railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/

Resources