How to add middleware in grape? - ruby-grape

I need to add a middleware to grape like this:
class AfterFailure < Grape::Middleware::Base
def call!(env)
#env = env
before
error = catch (:error) do
#app_response = #app.call(#env)
return after || #app_response
end
after_failure(error) || throw(:error, error) end
def after_failure(error)
puts "After Failure"
nil end
end
But I don't know how to add it to a api, How can I do it?

Got it. check the source code, found:
use AfterFailure

Related

Best practice of error handling on controller and interactor

# users_show_controller.rb
class Controllers::Users::Show
include Hanami::Action
params do
required(:id).filled(:str?)
end
def call(params)
result = users_show_interactor(id: params[:id])
halt 404 if result.failure?
#user = result.user
end
end
# users_show_interactor.rb
class Users::Show::Interactor
include Hanami::Interactor
expose :user
def call(:id)
#user = UserRepository.find_by(:id)
end
end
I have a controller and a interactor like above.
And I'm considering the better way to distinguish ClientError from ServerError, on the controller.
I think It is nice if I could handle an error like below.
handle_exeption StandardError => :some_handler
But, hanami-interactor wraps errors raised inside themselves and so, controller receive errors through result object from interactor.
I don't think that re-raising an error on the controller is good way.
result = some_interactor.call(params)
raise result.error if result.failure
How about implementing the error handler like this?
I know the if statement will increase easily and so this way is not smart.
def call(params)
result = some_interactor.call(params)
handle_error(result.error) if result.faulure?
end
private
def handle_error(error)
return handle_client_error(error) if error.is_a?(ClientError)
return server_error(error) if error.is_a?(ServerError)
end
Not actually hanami-oriented way, but please have a look at dry-monads with do notation. The basic idea is that you can write the interactor-like processing code in the following way
def some_action
value_1 = yield step_1
value_2 = yield step_2(value_1)
return yield(step_3(value_2))
end
def step_1
if condition
Success(some_value)
else
Failure(:some_error_code)
end
end
def step_2
if condition
Success(some_value)
else
Failure(:some_error_code_2)
end
end
Then in the controller you can match the failures using dry-matcher:
matcher.(result) do |m|
m.success do |v|
# ok
end
m.failure :some_error_code do |v|
halt 400
end
m.failure :some_error_2 do |v|
halt 422
end
end
The matcher may be defined in the prepend code for all controllers, so it's easy to remove the code duplication.
Hanami way is validating input parameters before each request handler. So, ClientError must be identified always before actions logic.
halt 400 unless params.valid? #halt ClientError
#your code
result = users_show_interactor(id: params[:id])
halt 422 if result.failure? #ServerError
halt 404 unless result.user
#user = result.user
I normally go about by raising scoped errors in the interactor, then the controller only has to rescue the errors raised by the interactor and return the appropriate status response.
Interactor:
module Users
class Delete
include Tnt::Interactor
class UserNotFoundError < ApplicationError; end
def call(report_id)
deleted = UserRepository.new.delete(report_id)
fail_with!(UserNotFoundError) unless deleted
end
end
end
Controller:
module Api::Controllers::Users
class Destroy
include Api::Action
include Api::Halt
params do
required(:id).filled(:str?, :uuid?)
end
def call(params)
halt 422 unless params.valid?
Users::Delete.new.call(params[:id])
rescue Users::Delete::UserNotFoundError => e
halt_with_status_and_error(404, e)
end
end
end
fail_with! and halt_with_status_and_error are helper methods common to my interactors and controllers, respectively.
# module Api::Halt
def halt_with_status_and_error(status, error = ApplicationError)
halt status, JSON.generate(
errors: [{ key: error.key, message: error.message }],
)
end
# module Tnt::Interactor
def fail_with!(exception)
#__result.fail!
raise exception
end

How I create a simples questionnaire with Lita.io?

I try implementing a little questionnaire in Lita as the sample:
For which system do you want to open a call?
SYSInitials
What's your problem?
I forgot my password
Thanks! Your call was opened!
Any help how I can do this?
So, I'm try this:
module Lita
module Handlers
class Helpdesk < Handler
on :shut_down_complete, :clear_context
route(/^abrir chamado$/i, :abrir_chamado)
route(/^.*$/i, :motivo)
http.get '/info', :web
def motivo(response)
return unless context == 'abrir_chamado'
response.reply('Thanks! Your call was opened!')
clear_context
end
def abrir_chamado(response)
redis.set(:context, :abrir_chamado)
user = response.user
response.reply(
%(Hello #{user.name}, What is your problem?)
)
end
def context
#contetx ||= redis.get(:context)
end
def clear_context
redis.del(:context)
end
Lita.register_handler(Helpdesk)
end
end
end
But when I register, :informar_motivo route, after passing of the :abrir_chamado route, is matched :informar_motivo route too.
but I need:
me: abrir chamado
Lita: Hello Shell User, What is your problem?
me: I forgot my password
Lita: Thanks! Your call was opened!
I found a ugly solution, but works :P
module Lita
module Handlers
class Helpdesk < Handler
on :shut_down_complete, :clear_context
on :unhandled_message, :motivo
route(/^abrir chamado$/i, :abrir_chamado)
http.get '/info', :web
def motivo(payload)
response = payload[:message]
return unless context == 'abrir_chamado'
response.reply('Thanks! Your call was opened!')
clear_context
end
def abrir_chamado(response)
redis.set(:context, :abrir_chamado)
user = response.user
response.reply(
%(Hello #{user.name}, What is your problem?)
)
end
def context
#contetx ||= redis.get(:context)
end
def clear_context
redis.del(:context)
end
Lita.register_handler(Helpdesk)
end
end
end

How do I implement hashids in ruby on rails

I will go ahead and apologize upfront as I am new to ruby and rails and I cannot for the life of me figure out how to implement using hashids in my project. The project is a simple image host. I have it already working using Base58 to encode the sql ID and then decode it in the controller. However I wanted to make the URLs more random hence switching to hashids.
I have placed the hashids.rb file in my lib directory from here: https://github.com/peterhellberg/hashids.rb
Now some of the confusion starts here. Do I need to initialize hashids on every page that uses hashids.encode and hashids.decode via
hashids = Hashids.new("mysalt")
I found this post (http://zogovic.com/post/75234760043/youtube-like-ids-for-your-activerecord-models) which leads me to believe I can put it into an initializer however after doing that I am still getting NameError (undefined local variable or method `hashids' for ImageManager:Class)
so in my ImageManager.rb class I have
require 'hashids'
class ImageManager
class << self
def save_image(imgpath, name)
mime = %x(/usr/bin/exiftool -MIMEType #{imgpath})[34..-1].rstrip
if mime.nil? || !VALID_MIME.include?(mime)
return { status: 'failure', message: "#{name} uses an invalid format." }
end
hash = Digest::MD5.file(imgpath).hexdigest
image = Image.find_by_imghash(hash)
if image.nil?
image = Image.new
image.mimetype = mime
image.imghash = hash
unless image.save!
return { status: 'failure', message: "Failed to save #{name}." }
end
unless File.directory?(Rails.root.join('uploads'))
Dir.mkdir(Rails.root.join('uploads'))
end
#File.open(Rails.root.join('uploads', "#{Base58.encode(image.id)}.png"), 'wb') { |f| f.write(File.open(imgpath, 'rb').read) }
File.open(Rails.root.join('uploads', "#{hashids.encode(image.id)}.png"), 'wb') { |f| f.write(File.open(imgpath, 'rb').read) }
end
link = ImageLink.new
link.image = image
link.save
#return { status: 'success', message: Base58.encode(link.id) }
return { status: 'success', message: hashids.encode(link.id) }
end
private
VALID_MIME = %w(image/png image/jpeg image/gif)
end
end
And in my controller I have:
require 'hashids'
class MainController < ApplicationController
MAX_FILE_SIZE = 10 * 1024 * 1024
MAX_CACHE_SIZE = 128 * 1024 * 1024
#links = Hash.new
#files = Hash.new
#tstamps = Hash.new
#sizes = Hash.new
#cache_size = 0
class << self
attr_accessor :links
attr_accessor :files
attr_accessor :tstamps
attr_accessor :sizes
attr_accessor :cache_size
attr_accessor :hashids
end
def index
end
def transparency
end
def image
##imglist = params[:id].split(',').map{ |id| ImageLink.find(Base58.decode(id)) }
#imglist = params[:id].split(',').map{ |id| ImageLink.find(hashids.decode(id)) }
end
def image_direct
#linkid = Base58.decode(params[:id])
linkid = hashids.decode(params[:id])
file =
if Rails.env.production?
puts "#{Base58.encode(ImageLink.find(linkid).image.id)}.png"
File.open(Rails.root.join('uploads', "#{Base58.encode(ImageLink.find(linkid).image.id)}.png"), 'rb') { |f| f.read }
else
puts "#{hashids.encode(ImageLink.find(linkid).image.id)}.png"
File.open(Rails.root.join('uploads', "#{hashids.encode(ImageLink.find(linkid).image.id)}.png"), 'rb') { |f| f.read }
end
send_data(file, type: ImageLink.find(linkid).image.mimetype, disposition: 'inline')
end
def upload
imgparam = params[:image]
if imgparam.is_a?(String)
name = File.basename(imgparam)
imgpath = save_to_tempfile(imgparam).path
else
name = imgparam.original_filename
imgpath = imgparam.tempfile.path
end
File.chmod(0666, imgpath)
%x(/usr/bin/exiftool -all= -overwrite_original #{imgpath})
logger.debug %x(which exiftool)
render json: ImageManager.save_image(imgpath, name)
end
private
def save_to_tempfile(url)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.start do
resp = http.get(uri.path)
file = Tempfile.new('urlupload', Dir.tmpdir, :encoding => 'ascii-8bit')
file.write(resp.body)
file.flush
return file
end
end
end
Then in my image.html.erb view I have this:
<%
#imglist.each_with_index { |link, i|
id = hashids.encode(link.id)
ext = link.image.mimetype.split('/')[1]
if ext == 'jpeg'
ext = 'jpg'
end
puts id + '.' + ext
%>
Now if I add
hashids = Hashids.new("mysalt")
in ImageManager.rb main_controller.rb and in my image.html.erb I am getting this error:
ActionView::Template::Error (undefined method `id' for #<Array:0x000000062f69c0>)
So all in all implementing hashids.encode/decode is not as easy as implementing Base58.encode/decode and I am confused on how to get it working... Any help would be greatly appreciated.
I would suggest loading it as a gem by including it into your Gemfile and running bundle install. It will save you the hassle of requiring it in every file and allow you to manage updates using Bundler.
Yes, you do need to initialize it wherever it is going to be used with the same salt. Would suggest that you define the salt as a constant, perhaps in application.rb.
The link you provided injects hashids into ActiveRecord, which means it will not work anywhere else. I would not recommend the same approach as it will require a high level of familiarity with Rails.
You might want to spend some time understanding ActiveRecord and ActiveModel. Will save you a lot of reinventing the wheel. :)
Before everythink you should just to test if Hashlib is included in your project, you can run command rails c in your project folder and make just a small test :
>> my_id = ImageLink.last.id
>> puts Hashids.new(my_id)
If not working, add the gem in gemfile (that anyway make a lot more sence).
Then, I think you should add a getter for your hash_id in your ImageLink model.
Even you don't want to save your hash in the database, this hash have it's pllace in your model. See virtual property for more info.
Remember "Skinny Controller, Fat Model".
class ImageLink < ActiveRecord::Base
def hash_id()
# cache the hash
#hash_id ||= Hashids.new(id)
end
def extension()
# you could add the logic of extension here also.
ext = image.mimetype.split('/')[1]
if ext == 'jpeg'
'jpg'
else
ext
end
end
end
Change the return in your ImageManager#save_image
link = ImageLink.new
link.image = image
# Be sure your image have been saved (validation errors, etc.)
if link.save
{ status: 'success', message: link.hash_id }
else
{status: 'failure', message: link.errors.join(", ")}
end
In your template
<%
#imglist.each_with_index do |link, i|
puts link.hash_id + '.' + link.extension
end # <- I prefer the do..end to not forgot the ending parenthesis
%>
All this code is not tested...
I was looking for something similar where I can disguise the ids of my records. I came across act_as_hashids.
https://github.com/dtaniwaki/acts_as_hashids
This little gem integrates seamlessly. You can still find your records through the ids. Or with the hash. On nested records you can use the method with_hashids.
To get the hash you use to_param on the object itself which result in a string similar to this ePQgabdg.
Since I just implemented this I can't tell how useful this gem will be. So far I just had to adjust my code a little bit.
I also gave the records a virtual attribute hashid so I can access it easily.
attr_accessor :hashid
after_find :set_hashid
private
def set_hashid
self.hashid = self.to_param
end

Ruby Devise 2.2 add email address along with the user.email

Currently I am using ruby devise gem 2.2.3. And I tried to customize the confirmation_instructions for adding couple of email ids with the user email.
app/mailers/my_devise_mailer.rb
class MyDeviseMailer < Devise::Mailer
include Devise::Mailers::Helpers
def confirmation_instructions(record, opts={})
opts[:to] = "example1#mail.com, example2#mail.com"
super
end
end
config/initializers/devise.rb
config.mailer = "MyDeviseMailer"
And, I ran the following in my console
user = User.first
MyDeviseMailer.delay.confirmation_instructions(user)
I got a output without body message. PFA
Correct
Wrong
Can anyone tell me what I missed to add/configure?
You could create a new mailer instead and modify the headers of that one:
# app/mailers/my_mailer.rb
class MyMailer < Devise::Mailer
def headers_for(action, opts)
if action == :confirmation_instructions
super.merge!(to: ['example1#mail.com', 'example2#mail.com'])
else
super
end
end
end
Then tell Devise to use your mailer:
# config/initializers/devise.rb
config.mailer = MyMailer
super.merge!" will not work. Because it'll replace the value for the given key (:to). But, the requirement is to add two mail id's with 'To'. The following gist is working fine.
class MyMailer < Devise::Mailer
include Devise::Mailers::Helpers
def headers_for(action, opts={})
begin
super.merge!(to: [super[:to], 'example1#mail.com', 'example2#mail.com'], template_path: ["devise/mailer"]) if action == :confirmation_instructions
rescue Exception => e
super
end
end
end
Happy Coding!!

Thor::Group do not continue if a condition is not met

I'm converting a generator over from RubiGen and would like to make it so the group of tasks in Thor::Group does not complete if a condition isn't met.
The RubiGen generator looked something like this:
def initialize(runtime_args, runtime_options = {})
super
usage if args.size != 2
#name = args.shift
#site_name=args.shift
check_if_site_exists
extract_options
end
def check_if_site_exists
unless File.directory?(File.join(destination_root,'lib','sites',site_name.underscore))
$stderr.puts "******No such site #{site_name} exists.******"
usage
end
end
So it'd show a usage banner and exit out if the site hadn't been generated yet.
What is the best way to recreate this using thor?
This is my task.
class Page < Thor::Group
include Thor::Actions
source_root File.expand_path('../templates', __FILE__)
argument :name
argument :site_name
argument :subtype, :optional => true
def create_page
check_if_site_exists
page_path = File.join('lib', 'sites', "#{site_name}")
template('page.tt', "#{page_path}/pages/#{name.underscore}_page.rb")
end
def create_spec
base_spec_path = File.join('spec', 'isolation', "#{site_name}")
if subtype.nil?
spec_path = base_spec_path
else
spec_path = File.join("#{base_spec_path}", 'isolation')
end
template('functional_page_spec.tt', "#{spec_path}/#{name.underscore}_page_spec.rb")
end
protected
def check_if_site_exists # :nodoc:
$stderr.puts "#{site_name} does not exist." unless File.directory?(File.join(destination_root,'lib','sites', site_name.underscore))
end
end
after looking through the generators for the spree gem i added a method first that checks for the site and then exits with code 1 if the site is not found after spitting out an error message to the console. The code looks something like this:
def check_if_site_exists
unless File.directory?(path/to/site)
say "site does not exist."
exit 1
end
end

Resources