I am trying to write a module and I want to replace that module with my action in my controller. For that I have created a module called test inside my controller folder which is. Where I want to put my action my controller action code is:
def test
rain_fall_type = "test"
year = ""
compare = params[:compare]
respond_to do |format|
format.html { render json: rain_fall_type }
end
end
I want to put this code inside my module code I have added this code into my module whose code is:
module Test
def test
rain_fall_type = "params[:rain_fall_type]
views = params[:views]"
year = ""
compare = params[:compare]
respond_to do |format|
format.html { render json: rain_fall_type }
end
end
end
And I am trying to extend this into my controller so I am putting extend Test into my controller but I am getting this error:
The action 'test' could not be found for ProductionProductivity7sController
When I remove def test from my module and put this code in controller like this:
def test
extend Test
end
And I remove def test from module and changed it to:
module Test
rain_fall_type = "params[:rain_fall_type]
views = params[:views]"
year = ""
compare = params[:compare]
respond_to do |format|
format.html { render json: rain_fall_type }
end
end
When I am doing this I am getting this error:
undefined local variable or method `params' for Test:Module
What should I do to just replace my test action into my module.
This is a good task for concerns, here is an example:
your controller code:
class ProductionProductivity7sController < ApplicationController
include Concerns::MyAwesomeModule
end
your module placed in app/controllers/concerns/my_awesome_module.rb:
module Concerns
module MyAwesomeModule
extend ActiveSupport::Concern
included do
# here you can use controller level methods
# like `before_action`
end
def the_action_name
# your logic goes here
# you can use all variables from original controller
# for e.g. `params`, `render` and so on.
end
end
end
Related
I am trying to retrieve data from a partial within one of my controllers without tripping a redirect/render issue.
This is what the create method looks like within the controller, which is calling another function.
def create
#finding = #findings.new(finding_params)
respond_to do |format|
if #finding.save
if #project_notifications.where(category: "XXXXXXX").any?
notify_users(1)
end
flash[:notice] = 'Finding was successfully created.'
else
helpers.show_errors_for(#finding)
end
format.html { redirect_back fallback_location: root_path }
format.js { head :no_head }
end
end
The notify_users function looks like this:
def notify_users(notification_type)
if notification_type == 1
html_body = render(partial: 'findings/added_finding')
subject = "XXXXXXX."
#project_notifications.each do |notification|
NotificationWorker.perform_async(html_body, notification.user, subject)
end
end
end
The problem with this is that I trip a multiple render/redirect error. Is there a way to retrieve data from the partial without calling render a second time?
You can use render_to_string for this use case:
def notify_users(notification_type)
if notification_type == 1
html_body = render_to_string(partial: 'findings/added_finding', layout: false)
subject = "XXXXXXX."
#project_notifications.each do |notification|
NotificationWorker.perform_async(html_body, notification.user, subject)
end
end
end
That way you'll get a stringified representation of your HTML to use elsewhere. Let me know how you get on with this - I'm keen to know whether this serves your use case. And any questions, fire away :)
In your notify_users method (not function), you're calling render here:
html_body = render(partial: 'findings/added_finding')
And in your create method, you're calling redirect here:
format.html { redirect_back fallback_location: root_path }
So, it would seem that the statement:
Render and/or redirect were called multiple times in this action.
...is true.
I suppose you could move notify_user to a NotifyUserService and do the rendering there. When I render in services, I do not have the Render and/or redirect were call multiple times problem.
So, you might do something like:
#app/services/notify_user_service.rb
class NotifyUserService
include ActsAsRendering
class << self
def call(args={})
new(args).call
end
end
def initialize(args)
args.each do |k,v|
class_eval do
attr_accessor k
end
send("#{k}=",v)
end
end
def call
case notification_type
when :some_notification_type
html_body = render_partial('findings/added_finding')
subject = "XXXXXXX."
project_notifications.each do |notification|
NotificationWorker.perform_async(html_body, notification.user, subject)
end
end
end
def some_information
case notification_type
when :some_notification_type
'some notification information'
else
'default notification information'
end
end
end
Then, in your controller,
def create
#finding = #findings.new(finding_params)
respond_to do |format|
if #finding.save
if #project_notifications.where(category: "XXXXXXX").any?
NotifyUserService.call(
notification_type: :some_notification_type,
project_notifications: #project_notifications
)
end
flash[:notice] = 'Finding was successfully created.'
else
helpers.show_errors_for(#finding)
end
format.html { redirect_back fallback_location: root_path }
format.js { head :no_head }
end
end
This, naturally, assumes that NotifyUserService knows how to render. I forget what the state of play is with rendering anywhere in the Rails 5/6 world is. But, to tell my services how to render, I have a module called something like ActsAsRendering:
#app/acts_as/acts_as_rendering.rb
#------------------------------------------------------------------------------------------------
# Use like:
# include ActsAsRendering
#------------------------------------------------------------------------------------------------
module ActsAsRendering
module ClassMethods
end
module InstanceMethods
private
def action_view
#action_view ||= new_action_view
end
def new_action_view
av = ActionView::Base.new
av.view_paths = ActionController::Base.view_paths
av.class_eval do
include Rails.application.routes.url_helpers
include ApplicationHelper
end
av
end
def method_missing(meth, *params, &block)
if action_view.respond_to?(meth)
action_view.send(meth, *params, &block)
else
super
end
end
def render_partial(file_name)
file_name = file_name.to_s
render(partial: file_name, locals: {presenter: self})
end
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.include InstanceMethods
end
end
You'll notice that I created a render_partial method and use that instead of render. The render_partial method passess the NotifyUserService instance in as a local. That way, in my views, I can do stuff like:
#app/views/findings/added_finding.html.haml
- #presenter = local_assigns[:presenter] if local_assigns[:presenter]
#some-document-id
.some-presenter-information
#presenter.some_information
And now the view will show 'some notification information' in the html. This way, I can push all view logic back into the presenter and my views become 100% logic free.
I have a controller
class Api::V1::InvoicesController < ApplicationController
def index
#invoices = Invoice.all
render json: #invoices, each_serializer: Api::V1::InvoicePreviewSerializer
end
end
On every single controller i will be specify that the serializer used is the name spaced with
Api::V1::
Then model name and then model name followed by PreviewSerializer
How could I on the appication controller specify that on every index action append each_serializer: Api::V1::MODEL_NAMEPreviewController?
I've not tested this but I think it should work like this:
# in the ApplicationController
def render(*args)
if action_name == 'index'
options = args.extract_options!
options[:each_serializer] = Api::V1::InvoicePreviewSerializer
args << options
end
super(*args)
end
Hope that works and helps!
How to extend a controller method from a Rails Engine without having to duplicate the whole thing?
Trying to extend https://github.com/radar/forem/blob/rails4/app/controllers/forem/forums_controller.rb -- app/decorators/controllers/forem/forums_controller_decorator.rb:
Ideal
Forem::ForumsController.class_eval do
def show
# A simple `include` here or something?
# New code goes here...
end
end
Current
Forem::ForumsController.class_eval do
def show
# Repeat ALL the code from:
# https://github.com/radar/forem/blob/rails4/app/controllers/forem/forums_controller.rb
authorize! :show, #forum
register_view
#topics = if forem_admin_or_moderator?(#forum)
#forum.topics
else
#forum.topics.visible.approved_or_pending_review_for(forem_user)
end
#topics = #topics.by_pinned_or_most_recent_post
# Kaminari allows to configure the method and param used
#topics = #topics.send(pagination_method, params[pagination_param]).per(Forem.per_page)
respond_to do |format|
format.html
format.atom { render :layout => false }
end
# New code goes here...
end
end
We use this gem for multiple applications and engines to do exactly what you want:
https://github.com/EPI-USE-Labs/activesupport-decorators
I could extend a controller method from a Rails Engine without having to duplicate code using alias_method
module ValueSets
SetsController.class_eval do
def new_with_authorize
new_without_authorize
authorize #value_set
end
alias_method :new_without_authorize, :new
alias_method :new, :new_with_authorize
end
end
i am trying to share an action between controllers using a concern like for example:
module Backend
module Exportable
extend ActiveSupport::Concern
def show
respond_to do |format|
format.xls { set_excel_headers "#{controller_name.classify}_#{params[:id]}_#{Time.now.to_i.to_s}.xls" }
end
end
end
end
Do you see any problems with this?
Should I never shared default actions from rails through concerns?
you have to create a file in concerns directory (named same as your module : backend.rb)
and here is your code :
module Backend
def show
respond_to do |format|
format.xls { set_excel_headers "#{controller_name.classify}_#{params[:id]}_#{Time.now.to_i.to_s}.xls" }
end
end
end
So in your controller you add :
extend Backend
I have a module in lib directory with the name "Transpo.rb":
module Transpo
class FT
def getCities
...
end
end
end
And in the controller I have
require 'Transpo.rb'
class TranspoController < ApplicationController
def index
#transpo = Transpo::FT.getCities()
respond_to do |format|
format.html # index.html.erb
format.json { render json: #transpo }
end
end
But when I run "http://localhost:3000/transpor" always gives the error:
NoMethodError in TranspoController#index
undefined method `getCities' for Transpo::FT:Class
Why? I've already set the auto_load lib in application.rb but continue with the same problem.
getCities is defined as an instance method, but you are calling it as a class method.
Either create an instance with something like instance = Transpo::FT.new, or change the definition of getCities to be def self.getCities to make it into a class method.