I realize this is against MVC principals and all, but how can I access the controller name/method in a module that is included in a class?
module DocumentHTMLBoxes
extend ActiveSupport::Concern
included do
def icon_or_text(variable)
if controller.action_name == 'send'
"<b>#{variable.name.capitalize}</b> #{variable.text}\n"
else
variable.format!
end
end
end
end
How can I access controller and/or controller.action_name in the module?
In Rails the view_context object contains all the ivars from the controller and includes all the helpers. It also provides access to the session, cookies and request. It is the implicit self when you are rendering templates.
Models do not have access to the view context - this is a conscious design as it gives a good seperation of concerns.
If you want to break the encapsulation you need to pass the context to the model.
module DocumentHTMLBoxes
extend ActiveSupport::Concern
included do
def icon_or_text(variable, context)
if context.action_name == 'send'
"<b>#{variable.name.capitalize}</b> #{variable.text}\n"
else
variable.format!
end
end
end
end
class Thing < ApplicationModel
include DocumentHTMLBoxes
end
Congratulations, you just created some really smelly code.
But, this is a really bad idea since it just adds one more responsibility to your models which are already near god-class status in Rails. Don't add generating HTML (a view/helper responsiblity!) to that list.
Instead you should just create a simple helper method:
module BoxesHelper
def icon_or_text(obj)
if context.action_name == 'send'
"<b>#{obj.name.capitalize}</b> #{obj.text}\n"
else
obj.format!
end
end
end
Or a decorator:
# #see https://ruby-doc.org/stdlib-2.1.0/libdoc/delegate/rdoc/Delegator.html
class Decorator < Delegator
attr_accessor :object
attr_accessor :context
def intialize(obj, cxt)
#object = obj
#context = cxt
super(obj) # pass obj to Delegator constructor, required
end
# Required by Delegator
def __getobj__
#object
end
def self.decorate(collection, context)
return collection.map { |record| self.new(record, context) }
end
end
module DocumentHTMLBoxes
extend ActiveSupport::Concern
included do
def icon_or_text(variable)
if context.action_name == 'send'
"<b>#{object.name.capitalize}</b> #{object.text}\n"
else
object.format!
end
end
end
end
class ThingDecorator < Decorator
include DocumentHTMLBoxes
end
To decorate a bunch of records in the controller you would do:
#things = ThingDecorator.decorate( Thing.all, self.view_context )
And now you can call icon_or_text on the decorated model:
<% #things.each do |t| %>
<% t.icon_or_text %>
<% end %>
I`d recommend refactor the code to make it cleaner:
Controller level call would be like
model_item.icon_or_text(variable, action_name)
The module
module DocumentHTMLBoxes
extend ActiveSupport::Concern
included do
def icon_or_text(variable, action_name)
if action_name == 'send'
"<b>#{variable.name.capitalize}</b> #{variable.text}\n"
else
variable.format!
end
end
end
end
Assuming you are including this concern into the controller then inside the icon_or_text method self is the controller. You will just be able to call action_name without prefixing it with controller.
module DocumentHTMLBoxes
extend ActiveSupport::Concern
included do
def icon_or_text(variable)
if action_name == 'send'
"<b>#{variable.name.capitalize}</b> #{variable.text}\n"
else
variable.format!
end
end
end
end
Related
I have and active admin resource. How i can dynamic extend resource. I try do it like this:
ActiveAdmin.register Order do
include UpdatePriceBlock
price_blocks_names names: [:last, :actual]
end
module UpdatePriceBlock
extend ActiveSupport::Concern
def price_blocks_names(options = {})
#price_blocks_names ||= options[:names]
end
def self.included(base)
#price_blocks_names.each do |name|
base.send :member_action, name, method: :get do
end
end
end
end
Now I has an error:
undefined method `price_blocks_names' for #<ActiveAdmin::ResourceDSL
This is a possible way, I don't know yet how you could keep the names inside the active admin register block. Add the price_blocks_names to your model:
class Order < ApplicationRecord
def self.price_bloks_names
%i(last actual)
end
end
And then place this in config/initializers/active_admin_update_price_block.rb
module ActiveAdminUpdatePriceBlock
def self.extended(base)
base.instance_eval do
self.controller.resource_class.price_bloks_names.each do |name|
member_action name, method: :get do
raise resource.inspect
end
end
end
end
end
Now you can extend, but the configuration needs to reside in the model as a class method this way. Haven't found a cleaner way so far.
ActiveAdmin.register Order do
extend UpdatePriceBlock
end
I think I found it:
ActiveAdmin.register Order do
controller do
include UpdatePriceBlock
end
end
What's going on:
Within the register Order do block, self is a special Active Admin thing:
ActiveAdmin.register Order do
puts "What's self here? #{self}"
end
=>
What's self here? #<ActiveAdmin::ResourceDSL:0x000000012b948230>
Within the controller do block, it's the controller class (so, pretty much the same as the body of a class definition):
ActiveAdmin.register Order do
controller do
puts "What's self here? #{self}"
include UpdatePriceBlock
end
end
=> What's self here? Admin::OrdersController
Within a member_action block, it's an instance of the controller, just like in a regular Rails controller action:
ActiveAdmin.register Order do
member_action :action do
puts "What's self here? #{self}"
end
end
=> What's self here? #<Admin::OrdersController:0x00000001259e7e80>
I am using a ActiveJob object to generate a PDF with wicked_pdf. To do this I need to extend some view things to be able to do this is from a Job object:
view = ActionView::Base.new(ActionController::Base.view_paths, {})
view.extend(ApplicationHelper)
view.extend(Rails.application.routes.url_helpers)
view.extend(Rails.application.routes.url_helpers)
This is all working fine, the PDF is created. But in the view I use decorators like this:
- decorate invoice do |decorates|
= decorates.title
And that is not working, probably because the object is not aware of these decorators, which are just PORO objects. So I guess I need to extend them too so they can be used here. How can I extend them? They are located in app\decorators
Edit:
The decorates method comes from a helper method:
module ApplicationHelper
def decorate(object, klass = nil)
unless object.nil?
klass ||= "#{object.class}Decorator".constantize
decorator = klass.new(object, self)
yield decorator if block_given?
decorator
end
end
end
And that helper method loads the correct decorator object:
class InvoiceDecorator < BaseDecorator
decorates :invoice
end
Which inherits from Base decorator:
class BaseDecorator
def initialize(object, template)
#object = object
#template = template
end
def self.decorates(name)
define_method(name) do
#object
end
end
end
My code:
I have used AuthenticationRelated only in the ApplicationHelper
also signed_in? in is_admin? is from Devise Gem.
module AuthenticationRelated
def is_admin?
athu = false
if signed_in?
current_user.roles.each do |role|
if role.name == 'admin'
athu = true
end
end
end
athu
end
end
Now I have a class SalesReportsGrid which I need to be able to access the is_admin?
So this is what I have done:
class SalesReportsGrid
include Datagrid
include AuthenticationRelated
scope do
if is_admin?
Sales.joins(product: [ category: [:access_lists] ] )
else
....
end
end
....
end
Now when I run this I get the following error:
undefined method `is_admin?` for SalesReportsGrid:Class
Edit
When I add extend AuthenticationRelated
This is what I get:
undefined method `signed_in?` for SalesReportsGrid:Class
I am really confused, can someone please have a look and suggest something?
Thanks
If you include module, you add its methods to instances of the class where you used include. To make module methods 'class methods' (i.e. singleton methods of class), you should use extend:
extend AuthenticationRelated
You need to be explicit in using current_user to define grid scope.
Here is example:
def index
#grid = SalesReportGrid.new(params[:sales_report_grid]) do |scope|
unless current_user.admin?
scope.where_accessible_by(current_user)
end
end
end
It allows you to be more explicit and don't move access control to the grid and leave it in controller like it use to happen in other places.
I created a module, basically what I want to do is,
in this module, there is a function that will work like before_filter. This function will perform the logic and determine what it should perform. Example
class JobsController < ApplicationController
include Mymodule
authorize_resources
def create
end
def update
end
end
module Mymodule
def authorize_resources
current_controller = params[:controller]
if current_controller == 'jobs'
//some logic
end
end
end
so how I actually can automatically detect the controller name based on where my function located such as jobs, users, and etc. This is something similar to CanCan, but I would like to make my own.
Besides, how can I raise an exception or redirect_to a path if it failed, is that need to extend some rails classes?
def authorize_resources
if current_controller.class == 'jobs'
//logic
end
end
Change your if to:
if(current_controller == JobsController)
If params[:controller] is the class itself, and
if(current_controller.class == JobsController)
If the variable is an instance of JobsController.
I have a common method that exists in my model because it is called by my model. Retrospectively, my view also requires this model method. In order to accomplish this, I have:
moved the model method to the application_helper.rb file
my model calls the application_helper method by adding include ApplicationHelper at the top of my ActiveRecord model
Functionality wise, it works. But is this good practice?
My Model looks like this:
class Client < ActiveRecord::Base
include ApplicationHelper
end
Writing include ApplicationHelper in to your model is bad practice because ApplicationHelper is a nice place to put tons of helper functions you need in your views. These functions will end up being imported as instance methods of your model. These functions are mostly unrelated to your model and will not work if they depend on things like params or request. Here are two other options:
Option 1:
You can just define the method inside the Client class, and then call it from the view, like this:
class Client < ActiveRecord::Base
def self.my_class_method
end
def my_instance_method
end
end
And then in your view:
<%= Client.my_class_method %>
<%= #client.my_instance_method %>
Option 2:
Make a separate module in lib and include it in the places you need it. The file name should match the module name for auto-loading to work.
In lib/my_module.rb:
module MyModule
def my_method
end
end
In your model:
class Client < ActiveRecord::Base
include MyModule
def other_method
my_method
end
end
Include the module in ApplicationHelper so it is available to all your views:
module ApplicationHelper
include MyModule
end
Then in your view you can call it easily:
<%= my_method %>
If you do want to move it to a helper, you should move it in to the client_helper, as it is something just for your Client model and not for the whole application.
The method you speak of though, is it a static class method or an instance method? If it's an instance method, then your models (even if they're in views) can call that method. If it's a static class method, then your views can use it too by calling it like any other static class method (i.e, Client.do_method or something).
I don't see any reason why it needs to be in a helper, unless your method has absoloutely nothing to do with your model, in which case that would be a different question.
5 Common method in model and helper:
Option 1:
You can just define the method inside the Client class, and then call it from the view,
like this:
class Client < ActiveRecord::Base
def self.my_class_method
end
def my_instance_method
end
end
And then in your view:
<%= Client.my_class_method %>
<%= #client.my_instance_method %>
Option 2:
module MyModule
def my_method
end
end
Include the module in ApplicationHelper so it is available to all your views:
module ApplicationHelper
include MyModule
end
Then in your view you can call it easily:
<%= my_method %>
option 3:
module MyModule
def my_method(amount)
end
end
In your model:
class Client < ActiveRecord::Base
include MyModule
def self.other_method(amount)
my_method(amount)
end
end
Then in your view you can call it easily:
<%= Client.other_method(amount) %>
Option 4:
Or you can declare the helper method as a class function, and use it like so:
module Myhelper
def self.my_village
"bikharniyan kallan,Rajasthan,India"
end
end
Then in your view you can call it easily:
<%= Myhelper.my_village %>
option 5:
use many helper in controller
helper=>
module Myhelper
def my_info
"my information is"
end
end
module Myhelper1
def my_village
"bikharniyan kallan,Rajasthan,India"
end
end
ApplicationHelper=>
module ApplicationHelper
include Myhelper
include Myhelper1
end
ApplicationController
class ApplicationController < ActionController::Base
include ApplicationHelper
end
YourController
class YourController < ActionController::Base
def action
my_info
my_village
end
end
At this moment, with rails 5 you can simply push your common method into application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.common_class_method
# some awesome implement
end
def common_method
# implement this
end
end
Then in each model class you can call common_class_method by : YourClassName.common_class_method
or call common_method by: YourClassInstance.common_method