Ruby Mixins and Instance variables - ruby

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.

Related

Ruby mixins looking for a best practice

I'm writing Ruby Gem where I have Connection module for Faraday configuration
module Example
module Connection
private
def connection
Faraday.new(url: 'http://localhost:3000/api') do |conn|
conn.request :url_encoded # form-encode POST params
conn.response :logger # log requests to STDOUT
conn.adapter Faraday.default_adapter # make requests with Net::HTTP
conn.use Faraday::Response::ParseJson
conn.use FaradayMiddleware::RaiseHttpException
end
end
end
end
Second module which makes API requests looks like this:
module Example
module Request
include Connection
def get(uri)
connection.get(uri).body
end
def post(url, attributes)
response = connection.post(url) do |request|
request.body = attributes.to_json
end
end
def self.extended(base)
base.include(InstanceMethods)
end
module InstanceMethods
include Connection
def put(url, attributes)
response = connection.put(url) do |request|
request.body = attributes.to_json
end
end
end
end
end
Class Cusomer where I use Request looks like this:
module Example
class Customer
extend Request
attr_accessor :id, :name, :age
def initialize(attrs)
attrs.each do |key, value|
instance_variable_set("##{key}", value)
end
end
def self.all
customers = get('v1/customer')
customers.map { |cust| new cust }
end
def save
params = {
id: self.id,
age: self.age
name: self.name,
}
put("v1/customers/#{self.id}", params)
end
end
end
So here you see in Customer#all class method I'm calling Request#get method which is available because I extended Request in Customer. then I'm using self.extended method in Request module to be make Request#put available in Customer class, so I have question is this good approach to use mixins like this, or do you have any suggestion?
Mixins are a strange beast. Best practices vary depending on who you talk to. As far as reuse goes, you've achieved that here with mixins, and you have a nice separation of concerns.
However, mixins are a form of inheritance (you can take a peek at #ancestors). I would challenge you saying that you shouldn't use inheritance here because a Customer doesn't have an "is-a" relationship with Connection. I would recommend you use composition instead (e.g. pass in Connection/Request) as it makes more sense to me in this case and has stronger encapsulation.
One guideline for writing mixins is to make everything end in "-able", so you would have Enumerable, Sortable, Runnable, Callable, etc. In this sense, mixins are generic extensions that provide some sort of helpers that are depending on a very specific interface (e.g. Enumerable depends on the class to implement #each).
You could also use mixins for cross-cutting concerns. For example, we've used mixins in the past in our background jobs so that we could add logging for example without having to touch the source code of the class. In this case, if a new job wants logging, then they just mixin the concern which is coupled to the framework and will inject itself properly.
My general rule of thumb is don't use them if you don't have to. They make understanding the code a lot more complicated in most cases
EDIT: Adding an example of composition. In order to maintain the interface you have above you'd need to have some sort of global connection state, so it may not make sense. Here's an alternative that uses composition
class CustomerConnection
# CustomerConnection is composed of a Connection and retains isolation
# of responsibilities. It also uses constructor injection (e.g. takes
# its dependencies in the constructor) which means easy testing.
def initialize(connection)
#connection = connection
end
def all_customers
#connection.get('v1/customers').map { |res| Customer.new(res) }
end
end
connection = Connection.new
CustomerConnection.new(connection).all_customers

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'

Padrino controller abstraction

I've been trying Padrino framework in one of my project, and there is one thing that really annoys me. I want to implement just for instance a user registration process using OmniAuth and want to break my request handler (controller's action) to separate methods, like this:
get ":provider/callback" do
#user = find_the_user_by_oauth(request)
create_user unless #user
store_user_in_session
end
def find_the_user_by_oauth(request)
#...
end
def store_user_in_session
session[:user_id] = #user.id
end
I know it would be nicer to push the logic to the model layer, but my question is, how could I break a controller logic to separated methods and share information among them (like using instance variables). In Rails I created these methods in the private scope of my controller, but here I should extend the Application class because it throws Undefined method exception for the previous code. I tried Helpers, but helpers don't know the instance variables, so you should pass the variables every time.
What is the good way to make my controller actions clean in Padrino?
To define a method inside an Padrino Controller you can use define_method instead of def.
For your example, do something like this:
Admin.controllers :dummy do
define_method :find_the_user_by_oauth do |request|
request.params["username"]
# ...
end
define_method :store_user_in_session do
session[:user_id] = #user
end
get :test do
#user = find_the_user_by_oauth(request)
create_user unless #user
store_user_in_session()
session.inspect
end
end
Padrino runs the block sent to Admin.controllers using instance_eval.
See this answer for the differences https://stackoverflow.com/a/3171649 between define_method and def
possible offtopic, but would you consider to use Espresso Framework instead.
then you'll can solve your issue as simple as:
class App < E
def index provider, action = 'callback'
#user = find_the_user_by_oauth
create_user unless #user
store_user_in_session
end
private
def find_the_user_by_oauth
# provider, action are accessed via `action_params`
# action_params[:provider]
# action_params[:action]
end
def store_user_in_session
session[:user_id] = #user.id
end
end

Rails routing: Giving default values for path helpers

Is there some way to provide a default value to the url/path helpers?
I have an optional scope wrapping around all of my routes:
#config/routes.rb
Foo::Application.routes.draw do
scope "(:current_brand)", :constraints => { :current_brand => /(foo)|(bar)/ } do
# ... all other routes go here
end
end
I want users to be able to access the site using these URLs:
/foo/some-place
/bar/some-place
/some-place
For convenience, I'm setting up a #current_brand in my ApplicationController:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_brand
def set_brand
if params.has_key?(:current_brand)
#current_brand = Brand.find_by_slug(params[:current_brand])
else
#current_brand = Brand.find_by_slug('blah')
end
end
end
So far so good, but now I must modify all *_path and *_url calls to include the :current_brand parameter, even though it is optional. This is really ugly, IMO.
Is there some way I can make the path helpers automagically pick up on #current_brand?
Or perhaps a better way to define the scope in routes.rb?
I think you will want to do something like this:
class ApplicationController < ActionController::Base
def url_options
{ :current_brand => #current_brand }.merge(super)
end
end
This method is called automatically every time url is constructed and it's result is merged into the parameters.
For more info on this, look at: default_url_options and rails 3
In addition to CMW's answer, to get it to work with rspec, I added this hack in spec/support/default_url_options.rb
ActionDispatch::Routing::RouteSet.class_eval do
undef_method :default_url_options
def default_url_options(options={})
{ :current_brand => default_brand }
end
end

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

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

Resources