Creating Downloadable concern in Rails 4: Error with send_file - ruby-on-rails

I'm trying to create a Downloadable concern with Rails 4.0.4 that I can mix in to all of my downloadable resources. Let's use the Documents resource as an example of something that should be downloadable:
#documents_controller.rb
class DocumentsController < ApplicationController
include Downloadable
before_action :set_document
#Other actions....
#Download
def download
send #document.document_full_path
end
private
# Use callbacks to share common setup or constraints between actions.
def set_document
#document = Document.find(params[:id])
end
end
The Downloadable module looks like (simple for now, but I want to add some validation code to check if the file actually exists and is readable, plus stream it and/or use x-file, etc):
#app/controller/concerns/downloadable
module Downloadable extend ActiveSupport::Concern
def send(filepath)
send_file filepath
end
end
And in my routes I have defined a downloadable concern:
#routes.rb
MyApp::Application.routes.draw do
concern :downloadable do
get 'download', on: :member
end
resources :documents, concerns: :downloadable
root 'welcome#index'
end
When I spin up my puma server and try to go to 0.0.0.0:3000/documents/ I get an error:
ActionController::MissingFile in DocumentsController#index
Cannot read file _process_action_callbacks
Extracted source (around line #4):
3 def send(filepath)
4 send_file filepath
5 end
app/controllers/concerns/downloadable.rb:4:in `send'
Before refactoring this as a concern I had it working using send_file(#document.document_full_path) directly in the 'download' method of the DocumentsController. I didn't get any errors until I refactored it as a concern in the controller and in routes.rb. Does anyone know what could be causing this?

The problem here was namespace pollution; the 'send' method did not resolve to my Downloadable module. I'm not exactly sure where it did resolve but in the end all I did was rename the method to 'send_to_user'. I also could have fixed it by making the call with the scope explicitly resolved 'Downloadable::send', but I think this this syntax is less sugary than just renaming the method.
After this change my Downloadable concern looks like:
module Downloadable extend ActiveSupport::Concern
def send_to_user(args={})
send_file args[:filepath]
end
end
And in my documents controller I have:
#documents_controller.rb
class DocumentsController < ApplicationController
include Downloadable
before_action :set_document
#Other actions....
#Download
def download
send_to_user filepath: #document.document_full_path
end
private
# Use callbacks to share common setup or constraints between actions.
def set_document
#document = Document.find(params[:id])
end
end
routes.rb is the same as above.

Related

Rails: not a supported controller name

What is wrong with the way I named my routes?
Do I need to change my folder structure and my module name to
:car_registration
/app/controllers/car_registration/steps_controller.rb
I would prefer to use the formatting I have, if possible.
Routes
scope module: 'CarRegistration' do
resources :steps
end
Controller
/app/controllers/CarRegistration/steps_controller.rb
module CarRegistration
class StepsController < ApplicationController
include Wicked::Wizard
steps :step1, :step2, step3
def show
#form_object_model ||= form_object_model_for_step(step)
render_wizard
end
def update
#form_object_model = form_object_model_for_step(step)
render_wizard #form_object_model
end
private
def form_object_model_for_step(step)
"CarRegistration::#{step.camelize}".constantize.new
end
end
end
ERROR
'CarRegistration/steps' is not a supported controller name. This can
lead to potential routing problems. See
http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use
(ArgumentError)
It looks like you have at least two problems. You are using a folder structure like:
app/controllers/CarRegistration/steps_controller.rb
That is unconventional. Instead, it should be:
app/controllers/car_registration/steps_controller.rb
Then, your routes should look like:
scope module: :car_registration do
resources :steps
end

Encapsulating controller logic in rails

I have 2 controllers in rails with different authentications schemes,
but they do almost the same.
What is the best way in rails to encapsulate
the logic of a controller in another class or helper?
Sample:
def ControllerA < BasicAuthController
def create
blablacode
end
end
def ControllerB < TokenAuthController
def create
blablacode
end
end
Whats the proper way to do this? create a model with the code?
Create a helper? other?
The simplest thing is to make a module and then include it into the other controllers:
module ControllerMixin
def create
blablacode
end
end
The remaining question, though, is where to put this code such that it is works with Rails autoloader, since it needs to be loaded before the controllers. One way to do it would be to write the module to a file in the lib/ directory, then add that to the autoload paths (see auto-loading-lib-files-in-rails-4
Why don't you enable both schemes for a single controller? Especially if the only difference is Authentication. You could have two app/controllers/concerns to encapsulate both authentication methods and include Auth1 and include Auth2 for a single controller who is only responsible for whatever resource it manages.
Otherwise, services are the best approach to encapsulate controller logic.
Create a folder called services in your app folder and write PORO classes here. Say you have a few places in your app where you want to pay for stuff via make Stripe.
# app/services/stripe_service.rb
module StripeService
def customer(args)
...
end
def pay(amount, customer)
...
end
def reverse(stripe_txn_id)
...
end
end
# controller
StripeService.customer(data)
=> <#Stripe::Customer>
Or if you only need to do one thing.
# app/services/some_thing.rb
module SomeThing
def call
# do stuff
end
end
# controller
SomeThing.call
=> # w/e
If you need an object with multiple reponsibilities you could create a class instead.
class ReportingService
def initialize(args)
...
end
def query
...
end
def data
...
end
def to_json
...
end
end
https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
I do it something like this:
#app/services/my_app/services/authentication.rb
class MyApp::Services::Authentication
class < self
def call(params={})
new(params).call
end
end # Class Methods
#==============================================================================================
# Instance Methods
#==============================================================================================
def initialize(params)
#params = params
end
def call
... do a lot of clever stuff
... end by returning true or false
end
private
def params() #params end
end
Then:
class FooController < ApplicationController
before_action :authenticate
def authenticate
redirect_to 'some_path' unless MyApp::Services::Authenticate.call(with: 'some_params')
end
end
Short answer, i choose to create a Helper.
From all the suggestions in the answers
Create a Module:
Seems correct but it didnt feel right to have logic outside
the app directory. This wasnt an external module or library but
something very related to the logic of my app.
Integrate diferents authentications in one controller:
Was a good suggestion but i have to change all the logic of my app.
Create a Helpers:
It seems to me the better solution, i had the code on a helper, and
is inside the app directory, very near from the other logic.

How to create api documentation using apipie outside the controller

I am using apipie for API documentation.
The problem I am facing now is that the large amount of information to be provided for documentation is ruining my code structure in the controller.
How can I write the documentation outside the controller file,
so that the code structure will be readable?
You can find nice explanation on how to use apipie outside controller in this document https://ilyabylich.svbtle.com/apipie-amazing-tool-for-documenting-your-rails-api
To sum things up (example from link):
# app/docs/users_doc.rb
module UsersDoc
# we need the DSL, right?
extend Apipie::DSL::Concern
api :GET, '/users', 'List users'
def show
# Nothing here, it's just a stub
end
end
# app/controller/users_controller.rb
class UsersController < ApplicationController
include UsersDoc
def show
# Application code goes here
# and it overrides blank method
# from the module
end
end
super also needs to be included in the stubbed actions in the docs. In my app I created a controller that inherits from an existing controller defined in a gem, and if I don't include super in the stubbed action, I get unexpected behavior.
So:
# app/docs/users_doc.rb
module UsersDoc
# we need the DSL, right?
extend Apipie::DSL::Concern
api :GET, '/users', 'List users'
def show
super
end
end
# app/controller/users_controller.rb
class UsersController < ApplicationController
include UsersDoc
def show
# Application code goes here
end
end

Rails 4.1: Creating routes by calling method in controller

I want to create a method that, when called from a controller, will add a nested resource route with a given name that routes to a specific controller. For instance, this...
class Api::V1::FooController < ApplicationController
has_users_route
end
...should be equivalent to...
namespace :api do
namespace :v1 do
resources :foo do
resources :users, controller: 'api_security'
end
end
end
...which would allow them to browse to /api/v1/foo/:foo_id/users and would send requests to the ApiSecurityController. Or would it go to Api::V1::ApiSecurityController? It frankly doesn't matter since they're all in the same namespace. I want to do it this way because I want to avoid having dozens of lines of this:
resources :foo do
resources :users, controller: 'api_security'
end
resources :bar do
resources :users, controller: 'api_security'
end
Using a method is easier to setup and maintain.
I'm fine as far as knowing what to do once the request gets to the controller, but it's the automatic creation of routes that I'm a little unsure of. What's the best way of handling this? The closest I've been able to find is a lot of discussion about engines but that doesn't feel appropriate because this isn't separate functionality that I want to add to my app, it's just dynamic routes that add on to existing resources.
Advice is appreciated!
I ended up building on the blog post suggested by #juanpastas, http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4, and tailoring it to my needs. Calling a method from the controllers ended up being a bad way to handle it. I wrote about the whole thing in my blog at http://blog.subvertallmedia.com/2014/10/08/dynamically-adding-nested-resource-routes-in-rails/ but the TL;DR:
# First draft, "just-make-it-work" code
# app/controllers/concerns/user_authorization.rb
module UserAuthorization
extend ActiveSupport::Concern
module ClassMethods
def register_new_resource(controller_name)
AppName::Application.routes.draw do
puts "Adding #{controller_name}"
namespace :api do
namespace :v1 do
resources controller_name.to_sym do
resources :users, controller: 'user_security', param: :given_id
end
end
end
end
end
end
end
# application_controller.rb
include UserAuthorization
# in routes.rb
['resource1', 'resource2', 'resource3'].each { |resource| ApplicationController.register_new_resource(resource) }
# app/controllers/api/v1/user_security_controller.rb
class Api::V1::UserSecurityController < ApplicationController
before_action :authenticate_user!
before_action :target_id
def index
end
def show
end
private
attr_reader :root_resource
def target_id
# to get around `params[:mystery_resource_id_name]`
#target_id ||= get_target_id
end
def get_target_id
#root_resource = request.fullpath.split('/')[3].singularize
params["#{root_resource}_id".to_sym]
end
def target_model
#target_model ||= root_resource.capitalize.constantize
end
def given_id
params[:given_id]
end
end

where to put helper methods for controllers only?

I am looking to write certain methods for processing strings, and other tasks that take place in numerous of my controllers. I know its bad practice to include helpers in your controller, so I was just wondering, where is the best place to put application wide methods used in controllers?
I realize some of you will say to put them in models, but you have to realize that not all my controllers have an associated model. Any and all input would be appreciated.
I tend to put them into helpers. The fact that they are included in views
automatically haven't been a problem for me. You can also place them into
something like app/concerns/ or lib/
I don't like cluttering ApplicationController with private methods
because this often becomes a mess.
Example:
module AuthenticationHelper
def current_user
#current_user # ||= ...
end
def authenticate!
redirect_to new_session_url unless current_user.signed_in?
end
end
module MobileSubdomain
def self.included(controller)
controller.before_filter :set_mobile_format
end
def set_mobile_format
request.format = :mobile if request.subdomain == "m"
end
end
class ApplicationController
include AuthenticationHelper
include MobileSubdomain
end
If you need to use a method in the application scope then I would suggest that you keep those methods inside the application controller and in order to use them inside views.. declare those as helper methods.
For example,
class ApplicationController < ActionController::Base
helper_method :current_user, :some_method
def current_user
#user ||= User.find_by_id(session[:user_id])
end
def some_method
end
end
I would suggest to put them in lib folder. So for example I have:
lib/utils/string_utils
module StringUtils
def foo
...
end
end
class BarController < ActionController::Base
include StringUtils
end
This demonstrates good methodology called Fat model, Thin controller, in this case we are using Mixins instead of Models to separate logic but idea is same. You want your controllers as simple as possible.
It all depends on your needs. I will provide here 2 examples. Both of them are just a custom libraries, located under lib directory.
First example - "custom string processing"
# lib/filters.rb
module Filters
# Converts value to canonical view
def self.phone(value)
# remove all non-digits
clean_value = value.gsub(/\D/, '')
country_codes = configus.phone.country_codes
area_code = configus.phone.defaults.area_code
case clean_value.length
when 7
"#{area_code}#{clean_value}"
when 11
# remove country code only if phone starts with the allowed country code
if country_codes.include?(clean_value[0].to_i)
clean_value[1..-1]
else
clean_value
end
else clean_value
end
end
# usage
# app/api/phones_controller.rb
class Api::PhonesController < Api::ApplicationController
def exists
if params[:q]
clean_value = Filters.phone(params[:q])
...
end
end
end
Second example - helper for flash messages
# lib/flash_helper.rb
module FlashHelper
def flash_translate(key, options = {})
scope = [:flash, :controllers]
scope += params[:controller].split('/')
scope << params[:action]
t(key, {:scope => scope}.merge(options))
end
end
# app/application_controller.rb
class ApplicationController < ActionController::Base
include FlashHelper
end
# usage
# app/your_controller.rb
class YourController < ApplicationController
def create
#object = Object.new(params[:object])
if #object.save
flash[:success] = flash_translate(:success)
...
end
end
end
Note: do not forget to add lib dir to the autoload paths. In config/application.rb add/modify this line config.autoload_paths += %W(#{config.root}/lib).
So for me the answer is lib directory.
Starting from Rails 4 there is a dedicated folder for it app/controllers/concerns. So you can create a module there and then include it in a specific controller(s) or in ApplicationController if you need it available throughout all your controllers.
In case those methods are used in numerous controllers, I would define them in application_controller.rb. Every controller will inherits from it and will be capable to use any method defined there

Resources