Ruby inheritance and advised approach? - ruby

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.

Related

Ruby modules nested within classes

So I've made quite a large application in Ruby, but I've realised it's quite unorganised to have everything as an instance method in one huge class, so I want to split it up into nested modules just so it's a bit more organised. I have searched on StackOverflow but it seems that it's actually not that common to use modules nested in a class.
I'm trying to understand how nested modules work by using a simpler example class:
class Phones
include Apps
include Call
attr_accessor :brand, :model, :price, :smartphone
def initialize(brand,model,price,smartphone=true)
#price = price
#brand = brand
#model = model
#smartphone = smartphone
#task = 'stand-by'
end
module Apps
public def camera
#task = __method__.to_s
puts "Took a picture!"
self
end
public def gallery
#task = __method__.to_s
puts "Photos!"
self
end
end
module Call
public def scall
#task = __method__.to_s
puts "Ring ring!"
self
end
end
end
Then I'm trying to run:
s7 = Phones.new('Samsung','S7 Edge',3000).Apps.camera
But I keep getting this error:
...phones.rb:3:in `<class:Phones>': uninitialized constant Phones::Apps (NameError)
The problem is your include calls are before the actual module definitions.
When you write a class definition, everything in there is executed immediately except stuff like method definitions. For example:
class Foo
puts 1 + 1
end
will print 2 immediately, it will not wait until you say Foo.new.
One way to fix it would be to move the include calls to the end of the class definition, or the modules to the top. You can also separate out the nested modules:
class Phones
module Apps
# your stuff here
end
end
class Phones
module Call
# stuff
end
end
# just make sure the previous modules have been defined
# by the time this code is run
class Phones
include Call
include Apps
end

access an instance method of a class from another module's instance method

I have a class which declares a number of instance methods
class User < Group
def get_name
end
def show_profile
end
def get_task(task_id)
#some logic
end
end
And I want to call some of these methods from within a module. I can do it with Include statement.
include Users brings in all of the methods from User class. In this case I only want get_task and explicitly do not want get_name, show_profile etc..
I have a Tasks module which also has set of methods and one of those methods calls get_task method of User class.
module Tasks
class Project
def design
tid = 12
design_task = get_task(tid)
end
end
end
How this can be achieved without including other unnecessary methods of that class. Please help.
If the get_task method isn't depending on the user, you can use a class method like below. If it is depending on the user, you will have to get the server before you can run the method.
class User < Group
def get_name
end
def show_profile
end
def self.get_task(task_id)
#some logic
end
end
class Project
def design
tid = 12
design_task = User.get_task(tid)
end
end

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

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

Sharing variables across submodules and classes

I am trying to build a simple little template parser for self-learning purposes.
How do I build something "modular" and share data across it? The data doesn't need to be accessible from outside, it's just internal data. Here's what I have:
# template_parser.rb
module TemplateParser
attr_accessor :html
attr_accessor :test_value
class Base
def initialize(html)
#html = html
#test_value = "foo"
end
def parse!
#html.css('a').each do |node|
::TemplateParser::Tag:ATag.substitute! node
end
end
end
end
# template_parser/tag/a_tag.rb
module TemplateParser
module Tag
class ATag
def self.substitute!(node)
# I want to access +test_value+ from +TemplateParser+
node = #test_value # => nil
end
end
end
end
Edit based on Phrogz' comment
I am currently thinking about something like:
p = TemplateParser.new(html, *args) # or TemplateParser::Base.new(html, *args)
p.append_css(file_or_string)
parsed_html = p.parse!
There shouldn't be much exposed methods because the parser should solve a non-general problem and is not portable. At least not at this early stage. What I've tried is to peek a bit from Nokogiri about the structure.
With the example code you've given, I'd recommend using composition to pass in an instance of TemplateParser::Base to the parse! method like so:
# in TemplateParser::Base#parse!
::TemplateParser::Tag::ATag.substitute! node, self
# TemplateParser::Tag::ATag
def self.substitute!(node, obj)
node = obj.test_value
end
You will also need to move the attr_accessor calls into the Base class for this to work.
module TemplateParser
class Base
attr_accessor :html
attr_accessor :test_value
# ...
end
end
Any other way I can think of right now of accessing test_value will be fairly convoluted considering the fact that parse! is a class method trying to access a different class instance's attribute.
The above assumes #test_value needs to be unique per TemplateParser::Base instance. If that's not the case, you could simplify the process by using a class or module instance variable.
module TemplateParser
class Base
#test_value = "foo"
class << self
attr_accessor :test_value
end
# ...
end
end
# OR
module TemplateParser
#test_value = "foo"
class << self
attr_accessor :test_value
end
class Base
# ...
end
end
Then set or retrieve the value with TemplateParser::Base.test_value OR TemplateParser.test_value depending on implementation.
Also, to perhaps state the obvious, I'm assuming your pseudo-code you've included here doesn't accurately reflect your real application code. If it does, then the substitute! method is a very round about way to achieve simple assignment. Just use node = test_value inside TemplateParser::Base#parse! and skip the round trip. I'm sure you know this, but it seemed worth mentioning at least...

using the same bunch of attributes in many classes

Please help me out.
I need to use the same bunch of attributes in many classes. I would suggest to create module with predefined attributes and extend this module in every class
module Basic
#a=10
end
class Use
extend Basic
def self.sh
#a
end
end
puts Use.sh
but the output is empty. It seems like I missed something.
Maybe there is a better way to do that?
Your thoughts?
It's all about the self:
module Basic
#a=10
end
has self evaluating to Basic. You want it to evaluate to Use when the latter is extended:
module Basic
# self = Basic, but methods defined for instances
class << self
# self = Basic's eigenclass
def extended(base)
base.class_eval do
# self = base due to class_eval
#a=10
end
end
end
end
class Use
# self = Use, but methods defined for instances
extend Basic # base = Use in the above
class << self
# self = Use's eigenclass
def sh
#a
end
end
end
Use.sh # 10
What you're describing is the Flyweight design pattern. While some view this as rarely used in ruby ( http://designpatternsinruby.com/section02/flyweight.html ), others provide an implementation ( http://www.scribd.com/doc/396559/gof-patterns-in-ruby page 14 )
Personally, what I would do is to put all these attributes into a yaml file, and parse them either into a global variable:
ATTRIBUTES = YAML.load_file(File.expand_path('attributes.yml', File.dirname(FILE))
or a class method (with caching here, assuming you won't change the yml file while the app is running and need the new values). I'd suggest using ActiveSupport::Concern here as it's easier to read than the traditional way of mixing in class methods:
module Basic
extend ActiveSupport::Concern
module ClassMethods
def attributes_file
File.expand_path('attributes.yml', File.dirname(__FILE__))
def attributes
#attributes ||= YAML.load_file(attributes_file)
#attributes
end
end
module InstanceMethods
# define any here that you need, like:
def attributes
self.class.attributes
end
end
end
You can define methods for each of the attributes, or rely on indexing into the attributes hash. You could also get fancy and define method_missing to check if an attribute exists with that name, so that you don't have to keep adding methods as you want to add more attributes to the shared configs.

Resources