Is it possible to execute an action from within another action?
I'm looking for something in between of 'redirect' and 'render'. I don't want to tell the browser to make an extra request with 'redirect', and 'render' will only execute the view, not the action.
note: I'm using rails 3
#ben-holland Take a look at this http://symfony.com/doc/current/book/controller.html#forwarding
Put the action in a lib/ file and include it in both controllers
#lib/foo_controller_includes.rb
module FooControllerIncludes
def special_edit
do_some_stuff!
render :action=>"/full/path/to/file"
end
end
#app/controllers/bar_controller.b
class BarController < ApplicationController
include FooControllerIncludes
def edit
special_edit
end
end
Related
I have a requirement to need to validate presence of some params in certain situations. Here is the example of that :
In my user controller, for update action, I am required to validate the presence of these params. Same deal for car controller, update action as well, you could see recurring theme here. Params are additional_info.
My base controller provides additional_info_params which pulls the right data from the request.
Here is what I tried so far. I created a AR controller concern and included it in the controller, here is some code:
module ClassMethods
def require_additional_info_for(*methods)
binding.pry
return unless methods.include?(action_name)
if additional_info_params.empty?
head 400
end
end
end
My idea was to be able to define methods that require these params on the top of controller file, just like before_action from rails or skip_authorization_check from cancan. Like so:
MyController < BaseController
include Concerns::AdditionalInformation
require_additional_info_for :update
def update
...
end
end
This code above however does not work as I intended, mainly because this fires on the request class without much knowledge about the request (where I need to derive action name from via action_name).
So how can I do something like this?
Yes, you can, but i suggest you to use the before_action callback!
In a 'abstract' controller, register your method like this:
class SameController < ApplicationController
...
protected
def require_additional_params
render status: :unprocessable_entity if additional_info_params.empty?
end
end
After this, all the controllers who will use this methods, must extends SameController, and runs before_action passing the above method for the wanted actions, for example:
class UserController < SameController
before_action :require_additional_params, only: [:action1, :action2]
end
Note: You can put the require_additional_params in a module and include in your controller, or just put it in the ApplicationController
You might also look at making these regular strong params in the respective controller. It looks something like this:
def update_params
params.require(:car).permit(:engine, :wheels, :rims).tap do |car_params|
car_params.require(:engine)
end
end
This would expect a top-level :car key params (which it strips), and require an :engine param, but allow the other 2 (:wheels and :rims). If :engine isn't present, it will raise a ActionController::ParameterMissing (just like if :cars was missing)
This is straight from the action controller strong params docs (last example at bottom)
I'll sometimes throw these into separate private methods on the respective controller, so there would also possibly be a create_params method with different requirements. I prefer this method over using a custom method as a before_action.
I want to add a method called quick_view, which is basically a show method but much simpler and meant to be served with Ajax on hover on a product.
How can i achieve this ? How can i open the ProductsController of spree and add before_filters and also specify the appropriate routes..
Thanks !
Doco for that is at https://guides.spreecommerce.com/developer/logic.html
Create a class called app/controllers/products_controller_decorator.rb, and put something like this in it:
Spree::ProductsController.class_eval do
before_filter :my_filter, :only => :quick_view
def quick_view
# your code goes here
end
private
def my_filter
# code for your before filter goes here
end
end
As for the routes, you'll be able to add it in your routes.rb file just like any other route, but you'll need to specify that it's a spree route:
Spree::Core::Engine.routes.draw do
# Your route goes inside this block
end
If I am going about this wrong please let me know I can change it. I have a file in config/initializers/payload_signer.rb. I am trying to use this file in the controller that is called device_enrollment_controller.rb.
PayloadSigner.sign(get_profile)
get_profile is a method in the controller that gets the file I need and returns it. PayloadSigner references the other file. When I try to run this (keeping in mind im sure changes will have to be made in payload_signer for it work right) the error I get is uninitialized constant DeviceEnrollmentController::PayloadSigner. This leads me to believe I am referencing the payload_signer.rb file incorrectly. I have tried things like include and load but so far they are not working.
Any help or guidance is appreciated.
Rails Initializers are called before Controllers or Models. So it won't work. Initializers are not intended for this kind of use. Instead I suggest placing your code in a controller before_filter. Either in the ApplicationController or only in those controllers that require it (e.g. DeviceEnrollmentController). Something like this:
class DeviceEnrollmentController # Or ApplicationController
before_filter :sign_payload
protected
def get_profile
# Magic
end
def sign_payload
PayloadSigner.sign(get_profile)
end
end
EDIT: Another example:
class DeviceEnrollmentController
# The filter is only applied to the sign action
# (that's what the :only parameter does).
before_filter :sign_payload, :only => [:sign]
# Browsing to /show, you render this magic button of yours.
def show
# Render page that holds the button
end
# The magic button is bound to the /sign route.
# Clicking on the button calls this action.
def sign
# When you get here, the #sign_payload method
# has already been called.
end
protected
def get_profile
# Magic
end
def sign_payload
PayloadSigner.sign(get_profile)
end
end
In my Rails 3 app, I have a number of models with a boolean field disabled. In controllers for these models, I use a custom action disable to toggle the disabled field using Ajax.
Example (For client),
# routes.rb
resources :clients do
member do
get :toggle_disable, to: 'clients#disable', as: :disable
end
end
# clients_controller.rb
def disable
#client = Client.find(params[:id])
#client.update_attribute :disabled, !#client.disabled
render 'clients/update_client', format: :js
end
# update_client.js.erb
$('#client-<%= #client.id %>-details').html("<%= escape_javascript(render 'clients/client', client: #client) %>");
I have this code for at least ten resources in my application.
QUESTION
How do I go about DRYing up this code and add actions for these boolean fields dynamically? I could have gone with creating a parent controller or module but I am not sure how will I take care of the views code.
I should be able to do something like this
#clients_controller.rb
class ClientsController < ApplicationController
add_toggle_action :disable
end
Two main ways to share methods:
inheritance: define your action in ApplicationController
mixins: add your method in a module and include the module in the appropriate controllers
Since you want only some controllers to get the method, I'll head towards mixin.
Your controller action must use a view with a full path, not relative, something like:
render '/shared/clien/update', format: :js
Lastly, you'll have to define all routes.
I have a controller called ProjectsController. Its actions, by default, look for views inside app/views/projects. I'd like to change that path for all methods (index, show, new, edit etc...) in the controller.
For example:
class ProjectsController < ApplicationController
#I'd like to be able to do something like this
views_path 'views/mycustomfolder'
def index
#some code
end
def show
#some code
end
def new
#some code
end
def edit
#some code
end
end
Please note I am not changing each method with render but defining a default path for all of them. Is this possible? If so, how?
Thank you!
See ActionView::ViewPaths::ClassMethods#prepend_view_path.
class ProjectsController < ApplicationController
prepend_view_path 'app/views/mycustomfolder'
...
You can do this inside your controller:
def self.controller_path
"mycustomfolder"
end
If there's no built-in method for this, perhaps you can override render for that controller?
class MyController < ApplicationController
# actions ..
private
def render(*args)
options = args.extract_options!
options[:template] = "/mycustomfolder/#{params[:action]}"
super(*(args << options))
end
end
Not sure how well this works out in practice, or if it works at all.
You can add something like:
paths.app.views << "app/views/myspecialdir"
in the config/application.rb file to have rails look in another directory for view templates. The one caveat is that it'll still look for view files that match the controller. So if you have a controller named HomeController with the above config for the views it'll look for something named "app/views/myspecialdir/home/index.html.erb" to render.
If you want to change the default path for all your views at app level, you could do something like following -
class ApplicationController < ActionController::Base
before_action :set_views
private
def set_views
prepend_view_path "#{Rails.root.join('app', 'views', 'new_views')}"
end
end
And write all your views in the folder new_views following the same directory structure as original.
P.S. - This answer is inspired from #mmell's answer.
The accepted answer no longer works for me. After much wailing and gnashing of teeth, I've managed to find that if render is not called in the action, then the default_render method is called. In my case, I needed to override the default_render message in my controller as follows:
def default_render
render "path/to/views/#{action_name.to_s}"
end