Padrino controller abstraction - ruby

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

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

How to map routes to controllers in Sinatra?

I'd like to create a simple experimental MVC framework using Sinatra.
I'd like to define resources by name "pages" for example should resolve to:
/pages (index)
/pages/new
/pages/:id/show (show)
as WELL as map to app/controllers/PagesController.rb with corresponding get('/') to be responsible for the index, post('/pages/create') be responsible for creation, etc.
Trouble is even after reading the official documentation I'm terribly confused. I imagine I need to use non-classic Sinatra model for this, but could anyone point me in the right direction?
Thank you
If you want what I think you're wanting, I do this all the time. Initially for this scheme I used the travis-api source as a reference, but essentially what you want to do is extend Sinatra::Base in a "controller" class and then mount up your individual Sinatra "controllers" in rack, something like this:
module Endpoint
def self.included(base)
base.class_eval do
set(:prefix) { "/" << name[/[^:]+$/].downcase }
end
end
end
class Users < Sinatra::Base
include Endpoint
get '/' do
#logic here
end
get '/:id' do
#logic here
end
post '/' do
#logic here
end
patch '/:id' do
#logic here
end
end
class Posts < Sinatra::Base
include Endpoint
post '/' do
#logic here
end
end
and then something like this:
class App
require "lib/endpoints/users"
require "lib/endpoints/posts"
attr_reader :app
def initialize
#app = Rack::Builder.app do
[Users, Posts].each do |e|
map(e.prefix) { run(e.new) }
end
end
end
def call(env)
app.call(env)
end
end
You can adjust this to whatever you need, but the idea is the same, you separate your app into composable Sinatra applications that each have a prefix that they are mounted under using Rack. This particular example will give you routes for:
get '/users'
get '/users/:id'
post '/users'
patch '/users/:id'
get '/posts'
I'll give you a very simple example here:
Create a file controller.rb
get '/pages' do
#pages = Pages.all
erb :pages
end
Next create a views directory in the same folder as teh controller, and create a file named pages.html.erb
This is the corresponding view to your previously created controller action.
Here, you can type something like:
<% #pages.each do |p| %>
<%= p.title %>
<% end %>
Restart your server, visit localhost:PORT/pages and you will see a list of all your page titles.
You can check out this link for a simple sinatra tutorial - http://code.tutsplus.com/tutorials/singing-with-sinatra--net-18965
You can make this as complicated or as simple as you need. For example:
Rails makes a lot of magic happen under the hood, whereas Sinatra is more flexible at the cost of requiring you to implement some of this stuff yourself.
controller_map = {
'pages' => PagesController
}
post '/:controller/new' do
c = params[:controller]
module = controller_map[c]
module.create_new()
...
end
get '/:controller/:id/show' do
c = params[:controller]
id = params[:id]
module = controller_map[c]
module.get(id)
...
end

Setting instance variables in ActionMailer

I have around 30 mailer methods where I'm passing the user as an argument. Since I need access to the #user variable in the view, I'm having to set this instance variable in every mailer method, for example, send_x_email(user).
Normally this would be done in an initialize method, but I've read that mailers act a bit differently. Additionally, some of the methods take a different number of arguments (one just takes user, the other takes user and message).
I've investigated before_action callbacks and looked at this post
Setting instance variables in Action Mailer?
...but I'm still stuck.
I would appreciate any thoughts on how to simplify things and remove #user = user from the 30 or so methods in the mailer class. Cheers!
class ReminderSender < ActionMailer::Base
def send_commands_email(user)
#user = user
mail(to: #user.email,
subject: "All Commands",
from: "<commands##{ENV['DOMAIN']}>")
end
def send_attachment_warning(user, message)
#user = user
#message = message
mail(to: #user.email,
subject: "Attachment Warning",
from: "<attachments##{ENV['DOMAIN']}>")
end
end
Try defining a 'mail' method in your class and declaring an instance variable there e.g.
class YouMailer
def send_email(user, message)
subject = 'something'
body = message
mail(user, {subject: subject, body: body}})
end
def mail(user, options={})
#user = user
mail_options = {to: #user.email}.merge(options)
super(mail_options)
end
end
But you might need to specify the 'template_path' and 'template_name' options with that strategy.
My suggestion would be to keep things as they are. Having "#user = user" in all of your mailer methods out of necessity isn't bad.

I want to override authenticate_user and current_user method of devise gem

I want to override authenticate_user! and current_user method of devise gem in my application Controller can you please help me with regards to that
Thanks
You may be able to monkey-patch it like:
module Devise
module Controllers
module Helpers
def authenticate_user!
#do some stuff
end
end
end
end
But I would ask what the ultimate goal is, because Devise has some customizability built into it already, and overriding these methods makes me wonder "why use Devise at all?"
On overriding how a user is authenticated:
Devise uses Warden under the hood
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb
So you can just add a new strategy in Warden to authenticate your users. See
https://github.com/hassox/warden/wiki/Strategies
You should not need to override current_user. What challenge are you facing ?
Do you need a different model returned ?
If you want to add code to authenticate_user!
class DuckController < ApplicationController
before_action :authenticate_duck
...
private
def authenticate_duck
#use Devise's method
authenticate_user!
#add your own stuff
unless current_user.duck.approved?
flash[:alert] = "Your duck is still pending. Please contact support for support."
redirect_to :back
end
end
end
You have to create a custom class to override the default Devise behavior:
class CustomFailure < Devise::FailureApp
def redirect_url
#return super unless [:worker, :employer, :user].include?(scope) #make it specific to a scope
new_user_session_url(:subdomain => 'secure')
end
# You need to override respond to eliminate recall
def respond
if http_auth?
http_auth
else
redirect
end
end
end
And in your config/initializers/devise.rb:
config.warden do |manager|
manager.failure_app = CustomFailure
end
But I suggest check out the Devise documentation :)
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
At application_controller.rb you can overwrite just as you want:
def authenticate_user!
super # just if want the default behavior
call_a_method_to_something if current_user
# or
call_a_method_to_something if current_user.nil?
end

Testing #current_user method using RSpec

I've been trying to do this for a couple of days now, but I can't figure it out. I have the following code in my controller:
#some_object = #current_user.some_method
In my spec, I want to attach a should_receive hook on that method, but I can't make it work. I've tried all of these, but none of them work:
assigns[:current_user].should_receive(:some_method).at_least(:once) # expected 1, got 0
User.should_receive(:some_method).at_least(:once) # expected 1, got 0
How is the correct way of testing this? I'm running this in my spec, and login is working:
setup :activate_authlogic
...
UserSession.create(users(:rune))
Thanks!
One example comes from the Ruby on Rails Tutorial. Rather than setting and reading #current_user directly, it defines two helper methods:
def current_user=(user)
#current_user = user
end
def current_user
#current_user
end
Later, they access this method in the tests using the controller method:
def test_sign_in(user)
controller.current_user = user
end
Using this methodology, you should be able to use
controller.current_user.should_receive(:some_method).at_least(:once)
You can’t call something like in the controllers:
expect(current_user).to be_present
expect(user_signed_in?).to be_true
So to do so, you can do this :
module ControllerMacros
def current_user
user_session_info = response.request.env['rack.session']['warden.user.user.key']
if user_session_info
user_id = user_session_info[0][0]
User.find(user_id)
else
nil
end
end
def user_signed_in?
!!current_user
end
end
You can either include the ControllerMacros in the top of the controller spec or include it in the spec_helper.rb like so :
RSpec.configure do |config|
config.include ControllerMacros, type: :controller
end

Resources