I have created a local volt gem and I would like to write a simple api:
def add(a,b)
a+b
end
But I don't know where to write this code and how to require it from the app. The only place where I have got it working is in controllers/main_controller.rb of the new created gem:
module DtPicker
class MainController < Volt::ModelController
end
def self.add(a,b)
a+b
end
end
I think this is not the correct place but the lib folder. In this place I can't get it working. What am I doing wrong? Thanks.
I have to:
require 'volt/dt_picker'
where I want to use DtPicker.add.
The method is defined in the file lib/volt/dt_picker/dt_picker.rb:
module DtPicker
def self.add(a,b)
a+b
end
end
Related
I know that in Cucumber that I can create a module and then include that module into the World object and all the methods that I have created within the newly created module are available globally
module MyModule
def my_method
end
end
World(MyModule)
Now anywhere in my cucumber tests I can call my_method and it will work
The issue I see here and an issue I have come across is duplication of method names, as the application gets bigger or other testers/developers work on the application.
So if I was to wrap everything up in its own module and create module methods like so
module MyModule
def self.my_method
page.find('#element')
end
end
World(MyModule)
MyModule.my_method
# This will return undefined variable or method 'page' for MyModule module
So being new to using modules I have read that you can extend other modules so that you can use those methods within another module
So to access the page method I would need to access Capybara::DSL
module MyModule
extend Capybara::DSL
def self.my_method
page.find('#element')
end
end
World(MyModule)
MyModule.my_method now works, but my question is rather than extend individual namespaces for every module that I need access to is there a way to extend/include everything or is this a bad practice?
Another example of where things fail are when I try to access instances of a class
module SiteCss
def login_page
Login.new
end
end
class Login < SitePrism::Page
element :username, "#username"
end
module MyModule
extend Capybara::DSL
def self.my_method
page.find('#element')
login_page.username.set('username')
end
end
World(MyModule)
So with this example if I was it try and call login_page.username I would get
undefined method `login_page`
I'm really looking for the correct way to be doing this.
In conclusion I am trying to understand how to use custom modules and classes in cucumber without having to load everything into the World object.
Yes, it's not pretty to extend a module multiple times, if that's really what you want to do, I can suggest a way you can improve it a bit:
base class which will inherit your page object framework and extend(include is probably the correct option here):
module Pages
class BasePage < SitePrism::Page
extend Capybara::DSL
end
end
Now your login class:
module Pages
class Login < BasePage
element :username, "#username"
def yourmethod
page.find('#element')
username.set('username')
end
end
end
Now the bits you are probably interested in, expose yourmethod to cucumber world:
module SiteCss
def page_object
#page_object ||= Pages::Login.new
end
end
World(SiteCss)
Now you should have access to yourmethod in a cleaner way...
Usage:
#page_object.yourmethod
Although the most important suggestion I could give you is run from SitePrism... Create your own page object framework... don't fall into SitePrism trap...
I'm trying to call CurrentPlan::Subscription inside CurrentPlan using a method named subscriptions. I have 2 files. Here is my code
current_plan.rb
require_relative 'current_plan/subscription'
class CurrentPlan
# Get all subscriptions through current_plan
def subscriptions
CurrentPlan::Subscription.all
end
end
current_plan = CurrentPlan.new()
current_plan.subscriptions
current_plan/subscription.rb
require_relative '../current_plan'
class CurrentPlan::Subscription
def self.all
%w[subscription_1 subscription_2 subscription_3]
end
end
The Error I receive.
<CurrentPlan:0x0000010191ebd0>
/Users/foobar/Sites/ruby_apps/current_plan.rb:18:in `subscriptions': uninitialized constant CurrentPlan::Subscription (NameError)
from /Users/foobar/Sites/ruby_apps/current_plan.rb:25:in `<top (required)>'
from /Users/foobar/Sites/ruby_apps/current_plan/subscription.rb:3:in `require_relative'
from /Users/foobar/Sites/ruby_apps/current_plan/subscription.rb:3:in `<top (required)>'
from current_plan.rb:14:in `require_relative'
from current_plan.rb:14:in `<main>'
In current_plan/subscription.rb, change to:
require_relative '../current_plan'
class CurrentPlan
class Subscription
def self.all
%w[subscription_1 subscription_2 subscription_3]
end
end
end
Ruby gets confused trying to resolve CurrentPlan::Subscription when it doesn't even really know what CurrentPlan is...
An alternative would be to put the require statement in current_plan.rb at the end of the file (after the class declaration), in which case you would not need to modify subscription.rb, but that could cause other problems down the line.
There are two issues here. The first is that you are require-ing files in a circle. If you look at the backtrace of your error, starting at the bottom, the first thing to be executed is the call to require_relative 'current_plan/subscription'. The first line in that file is a call to require_relative '../current_plan', which is the original file which is already in the process of loading.
Ruby will now try to require this file, and will skip over the call to require_relative 'current_plan/subscription' since it recognises that the file has already been required (even though it hasn't finished being required). Defining the CurrentPlan class is okay, since even though the method references an undefined constant it isn’t called yet. When execution reaches the last two lines of current_plan.rb it creates a CurrentPlan instance and tries to call subscriptions on it. This is where the error occurs, since subscriptions refers to CurrentPlan::Subscription that hasn’t been defined yet (we still haven’t returned from the call to require_relative at the top of subscription.rb).
This behaviour only occurs when the file being required is the originally executed file, i.e. in this case you run $ ruby current_plan.rb and then call require 'current_plan' (or require_relative). If current_plan is required from another file then the require_relative '../current_plan' in subscription.rb will be skipped as Ruby will recognise that file as already having been required.
Fixing this is simple, jut remove the line
require_relative '../current_plan'
from subscription.rb. (You may need to make sure files other than current_plan.rb don’t try to require subscriptions directly).
Fixing that will expose the other problem. When trying to require subscription the class CurrentPlan hasn’t yet been defined, so class CurrentPlan::Subscription will fail.
There are a couple of ways to solve this, the most straightforward is to change
class CurrentPlan::Subscription
...
end
to
class CurrentPlan
class Subscription
...
end
end
so that you ensure CurrentPlan is defined when you try to define Subscription. Another way would be to move the call to require_relative in current_plan.rb below the definition of CurrentPlan (or even inside it).
Try this:
module A
class Subscription
def self.all
puts "all method of Subscription"
end
end
end
class CurrentPlan
include A
def subscriptions
CurrentPlan::Subscription.all
end
end
current_plan = CurrentPlan.new()
current_plan.subscriptions # => all method of Subscription
Well, if you want to inherit behaviour, simply use... well, inheritance. But be aware that Ruby does not support nested classes like, say, Java does.
If you want, the Inner class to extend the outer class, you can do:
class Outer
class Inner < Outer
def outer_method
my_outer_method("hey!")
end
end
def my_outer_method(foo)
puts foo
end
end
foo = Outer::Inner.new
foo.outer_method #<= "hey!"
foo.my_outer_method("hi!") #<= "hi!"
Which is simpler, by the way.
You can still think about namespaces/modules, like:
module CurrentPlan #this is your namespace
end
class CurrentPlan::Subscription #this is your class
end
subs = CurrentPlan::Subscription.new #this is your instance
Although, in Ruby you cannot have an instance of a module (in this case CurrentPlan cannot be instantiated). But you can include the module's methods by using extend.
This worked out for me! Appreciate everyone's help
current_plan.rb
require_relative 'current_plan/subscription'
class CurrentPlan
def subscription
CurrentPlan::Subscription.new
end
end
current_plan = CurrentPlan.new
puts current_plan.subscription.all
current_plan/subscription.rb
class CurrentPlan
class Subscription
def all
%w[asd asdadsadasdas njasdnjasndjasd subscription_1 subscription_2]
end
end
end
Assume I have the following two files:
app.rb
class App
def a
"a"
end
require_relative 'b'
end
b.rb
class App
def b
"b"
end
end
App in app.rb is being successfully extended by App in b.rb, but I have no idea why. Why does this work?
Your code is equivalent to:
class App
def a
"a"
end
end
class App
def b
"b"
end
end
You might suppose it is equivalent to:
class App
def a
"a"
end
class App
def b
"b"
end
end
end
It isn't, because the require'd file code is always executed in a global scope.
require_relative is pulling the code from b.rb into app.rb
Ruby is a dynamic language and allows you to open and close existing classes at will. Basically when you run app.rb, it reads method 'a' and then reads b.rb giving you access to method 'b' from the app.rb program.
Require_relative assumes the required file is in app.rb's root directory.
This becomes very useful in more complex situations when you might want to add methods to previouskly existing (Ruby Core) classes like Array.
Read this book for a more in depth understanding:
Metaprogramming with Ruby
I've defined a Sinatra helper in the usual way:
module Sinatra
module FooHelper
# code goes here
end
end
In my helper, among other things, I'd like to add a method to Numeric:
module Sinatra
module FooHelper
class ::Numeric
def my_new_method
end
end
end
end
However, in the interests of being unobtrusive, I only want to do add this method if my Sinatra helper is actually included in an application; if nobody runs helpers Sinatra::FooHelper, I don't want to affect anything (which seems like a reasonable thing to expect of a Sinatra extension).
Is there any hook that's fired when my helper is included, that would enable me to add my method only once that happens?
You can use the Module#included method to do this. I think you will need to modify your class definition slightly to use class_eval. I've tested the following and it works as expected:
module Sinatra
module FooHelper
def self.included(mod)
::Numeric.class_eval do
def my_new_method
return "whatever"
end
end
end
end
end
I have a situation where I can access a module's functions from one file but not another. These files are both in the same directory. I'll try to recreate the code the best I can:
Directory Structure:
init.rb
lib/FileGenerator.rb
lib/AutoConfig.rb
lib/modules/helpers.rb
lib/AutoConfig.rb
#!/usr/bin/env ruby
require 'filegenerator'
require 'modules/helpers'
class AutoConfig
include Helpers
def initialize
end
def myFunction
myhelper #here's the module function
end
def mySecondFunction
FileGenerator.generatorFunction # call to the FileGenerator
end
end
lib/FileGenerator.rb
#!/usr/bin/env ruby
require 'modules/helpers'
class FileGenerator
include Helpers
def initialize
end
def self.generatorFunction
myhelper #here's the module function that doesn't work
end
end
lib/modules/helper.rb
#!/usr/bin/env ruby
module Helpers
def myhelper
#Do Stuff
end
end
The AutoConfig file is the main workhorse of the app. When it calls to the myhelper module function it gives me no problems what-so-ever. The AutoConfig part way through calls the FileGenerator.generatorFunction.
The FileGenerator.generatorFunction also contains this same module function, but for some reason when I run the program I get the following error:
filegenerator.rb:26:in `generatorFunction': undefined method `myhelper' for FileGenerator:Class (NoMethodError)
I've been at this now for several hours trying many different combinations and can't figure out where I'm going wrong. Any help would be appreciated.
generatorFunction is a class method. It doesn't see instance-level methods. And myhelper (brought in by include Helpers) is an instance method. To remedy that, you should extend Helpers instead. It works like include, but makes class methods.
class FileGenerator
extend Helpers
end
BTW, the name generatorFunction is not in ruby style. You should name methods in snake_case (generator_function).