How do I call a method from a module in a view?
Made in /lib folder "util.rb"
module Util
def something
....
end
end
in controller
require 'util'
in view
<% name = ??? Util.something ??? %>
It'll be great to see the actual use case as there are a couple of ways to do it. I'd also recommend using a helper for views as that's what they are designed for.
But in your case, the only thing to make it work, is to set a self on that method.
module Util
def self.something
puts 'hi'
end
end
then you can easily call it in your view:
<% hello = Util.something %>
Related
Let's say I have the following method:
def run
#users.each do |u|
..
..
end
end
I have a lot of code in run so I am trying to refactor it and splitting it up into smaller methods. One of these methods is the following:
def finish_mutation
..
..
Rails.logger.info "Succesfully added #{u.name}"
end
This breaks because finish_mutation doesn't have access to the u variable. How can I create new methods that can access the u variable that I created in run?
You can simply create method taking parameter:
def finish_mutation(user)
# code
Rails.logger.info "Successfully added #{user.name}"
end
and call it, passing User instance:
finish_mutation(u)
it's easy to do you just add a parameter to your finish_mutation method like this :
def finish_mutation(param)
# .......
end
then you call your function like this :
def run
#users.each do |u|
..
..
finish_mutation(u) # <----- for example here you call your method
end
end
Sometimes passing your loop variable (as shown in the other answers) is the best answer. Sometimes you can DRY things up better by adding a method to whatever class 'u' is an instance of. So you might do
class User
def finish_mutation
# put your operation here
end
end
And then in your loop
u.finish_mutation
Obviously you need to think about which is the best way for a specific case.
EDIT:
I got many responses with different approaches for solving the problem, thanks a lot!
Sadly, none of them worked until now.
To easily understand and reproduce the failure, I created a small Rails repo on GitHub with a Rspec suite.
One of the specs is passing (where the presenter is initialized in the view).
One of the specs is failing (where the presenter is initialized in the controller).
How make them both pass ?
ORIGINAL QUESTION BELOW:
This is my Presenter:
class UserPresenter
def initialize(user, vc)
#user = user
#vc = vc
end
def linkify()
#
# HERE IS THE PROBLEM
#
vc.link_to("foo") do
yield
end
end
end
This is my Controller:
I initialize my Presenter in the controller, passing the view context of the controller with the presented model.
class UserController
def show
#user = User.find(#.....
#presenter = UserPresenter.new(#user, view_context)
end
end
In my Slim template, I call my Presenter to put the content in a link:
=#presenter.linkify do
p "123"
My problem is, I can't pass the block from the view to my linkify method.
In the with comment marked above code, the passed block is the whole view content, instead of the p 123.
When I initialize my Presenter in the view via: #presenter = UserPresenter.new(#user, self), it works as expected.
How I can make the linkify method uses the provided block, without initializing the presenter in the view ?
Because if you are going to use the yield command, you mustn't specify the &block, since now you are effectively receiving a block as a parameter using normal parameter syntax.
class UserPresenter
def initialize(user, vc)
#user = user
#vc = vc
end
def linkify() # <-- Remove &block
vc.link_to("foo") do
yield
end
end
end
# ...
# Somewhere else, assuming you have access to #presenter which is an instance of
# UserPresenter
# ...
def show
#presenter.linkify do
# ...
# do my view stuff here
# ...
end
end
show()
# Now, if your "View" is nothing but a block that needs to get passed in
# then you'd do this...
def show(&block)
#presenter.linkify do
block.call()
end
end
# This would be used this way:
show(lambda {
# ...
# View stuff here
# ..
})
As specified in lacrosse's answer. The wrong view context is the root of this cause. I tried to make a work around for your situation. And, this is how ended up doing it:
I created a helper method in ApplicationHelper:
module ApplicationHelper
def link(presenter)
presenter.linkify(self) do
yield
end
end
end
changed linkify() to:
def linkify(vc)
vc.link_to("foo") do
yield
end
end
which means, no need to have vc in presenter's class constructer, or you can update the vc in link method defined in the helper(your choice).
views are now looks something like this:
presenter_from_view.html.slim:
-#presenter = UserPresenter.new(#user, self)
=link #presenter do
p 123
presenter_from_controller.html.slim:
=link #presenter do
p 123
I agree, maybe this is not how you wanted your solution to be done. But, I couldn't get any cleaner work around for this. However, here you don't have to worry about passing self in views wherever you use link #presenter do..(which may become too much for writing code when you use linkify in multiple views I guess).
P.S.: Your all specs are passing now. And, if you need the modified code then I can push it to your repository in a separate branch. Let me know.
From Slim's documentation on Helpers and Capturing:
module Helpers
def headline(&block)
if defined?(::Rails)
# In Rails we have to use capture!
"<h1>#{capture(&block)}</h1>"
else
# If we are using Slim without a framework (Plain Tilt),
# this works directly.
"<h1>#{yield}</h1>"
end
end
end
Can you try using capture as follows?
def linkify(&block)
result = capture(&block)
vc.link_to("foo") do
result
end
end
The wrong view context is causing this issue. Just change UserPresenter#initialize to not accept view context, initialize presenter in the controller and pass the correct view context from the view instead, like so:
= #presenter.linkify(self) do
p "123"
What error are you getting? Just looking at the code...
In this method
def linkify()
#
# HERE IS THE PROBLEM
#
vc.link_to("foo") do
yield
end
end
where is vc defined?
I think you mean #vc which is the instance variable you're initializing.
Also as a side note... the empty () in linkify() are redundant in a ruby method with no variables. You can eliminate them.
Also you may want to take a look at the cells gem. As you're basically mirroring this behavior in your presenters and IMO cells is a cleaner way of accomplishing this in rails.
I think I figured out WHY it's not working. When you pass in the view_context in the controller, it's rendering the view_context once when you pass it in to the presenter, and then you again when you actually render the view.
def initialize(user, vc)
#user = user
#vc = vc
end
# When called from the view:
#presenter = UserPresenter.new(#user, self)
# You're passing the view directly in "as is" and renders as expected.
# However, when you pass it in from the controller:
#presenter = UserPresent.new(#user, view_context)
# you're essentially rendering the view_context in the controller, and then again
# once it renders at the end of your action. That's why you're getting this:
# "<p>123</p><p>123</p>"
You'll either need to send self in from the view each time you call linkify, or you can use an application helper method that will always have the view context.
I've got a lot of code like this in my app:
if #document.template.name == "Newsletter"
...
end
Which I realise is poor and ugly code. I'm not sure though what alternatives exist for this kind of code. Are there any best practices for it? I hope so. Cheers!
Sample controller code
In this controller code sample it posts an image to Twitter if the name is "Newsletter". I know it's messy, and that a lot of the code should be moved to the model. I'm more concerned about the conditional though.
if #document.template.name == "Newsletter"
source = Magick::Image.read(#document.component.image_newsletter.path).first
overlay = Magick::Image.read(#document.user.logo.path).first.resize_to_fit(source.columns)
rec = Magick::Draw.new
rec.stroke = "##{#document.user.colour1}"
rec.fill = "##{#document.user.colour1}"
rec.rectangle 0, 0, source.rows, 5
lank = source.extent(source.columns, source.rows+overlay.rows, 0 ,0)
combo = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(combo)
client.update_with_media("#{#document.title}: #{#document.remove_html(#document.components.first.body[0..100])}...", open(combo.to_blob))
else
client.update("#{#document.title}: #{#document.remove_html(#document.components.first.body[0..100])}... http://domain.com#{share_path(#document.user.ftp, #document)}")
end
Presenter Pattern to the rescue
app/helpers/application_helper.rb
This will give you convenient access to instantiate a presenter anywhere in any of your views.
Example, if you use present #document it will instantiate a DocumentPresenter.
module ApplicationHelper
def present object, klass = nil
klass ||= "#{object.class}Presenter".constantize
presenter = klass.new object, self
yield presenter if block_given?
presenter
end
end
To override the presenter used, you can do present #document, MyPresenter
app/presenters/document.rb
Your actual presenter. Create as many instance methods as you like and keep all of the view logic in here. You have access to all view helper methods through #template
class DocumentPresenter
def initialize document, template
#document = document
#template = template
end
def name
if #document.template.name == "Newsletter"
# for example ...
#template.link_to 'Newsletter', #template.document_index_path
end
end
def description
#template.content_tag :p, #document.description, class: "description"
end
end
app/views/document/show.html.erb
<% present #document do |document_presenter| %>
<div id="document">
<%= document_presenter.description %>
<%= document_presenter.name %>
</div>
<% end %>
Result
<div id="document">
<p class="description">
lorem ipsum
</p>
Newsletters
</div>
You can learn more about the Presenter Pattern as done by Ryan Bates in his RailsCast episode "Presenters from Scratch"
The only alternative I can think of presently is to move the template-specific code in to the Template model, separated in to individual methods which follow a particular naming convention.
For example, your methods could follow the convention process_x, where x is the name of the template. In this case, the code you posted for the "newsletter" would be in a method called process_newsletter.
I would also create a single point of entry, lets call it process, in the same model, which is responsible for delegating to one of these methods, like so:
class Template < ActiveRecord::Base
... other model code
def process # this is the method to be called from the controller
method_name = "process_#{self.name}" # the name of the method to be called
send method_name # call a method by this name
end
def process_newsletter
# your newsletter code already posted
end
def process_article # another example for illustration purposes
# article specific code
end
end
This not only eliminates the need for template name checking, but also helps to further separate your code, and moves any model-specific stuff away from the controller.
I have a model called Snippet, which contains snippets of HTML to push into views.
The model has a column CODE and another CONTENT
I'd like to write something like this in my view and get the content back
<%= raw Snippet.PHONE_NUMBER %>
which looks up PHONE_NUMBER on CODE and returns CONTENT
Add a method_missing class method in Snippet class as follows
# Snippet class
class << self
def method_missing(method, *args, &block)
if(snippet = Snippet.find_by_code(method.to_s))
return snippet.content
else
return super(method, *args, &block)
end
end
end
This should do the trick.
However, on a related note, I'm not sure if doing this would be the best way to go because your code is dependent on the data in your database. Tomorrow, the record for phone number gets removed and your code Snippet.PHONE_NUMBER would break. There is a lot maintenance headache in this approach.
A cleaner approach (which would avoid metaprogramming) would have your view do something like this:
<%= snippet :PHONE_NUMBER %>
or
<%= snippet 'PHONE_NUMBER' %>
where the snippet method is defined in a helper module like this:
module SnippetHelper
def snippet(code)
raw Snippet.find_by_code(code.to_s).content
end
end
and made available to all your views with something like this:
class ApplicationController < ApplicationController::Base
helper :snippet
end
Or use delegate.
But it sounds like you're providing another implementation of partials, or helpers, or a combination of decent_exposure and some combination of helpers and partials.
How can I make this method, which outputs a yellow line in the log file, accessible from everywhere (Models, Controllers, Views) in my Rails app?
def my_log(text, file = "", line = "")
text.to_s.chomp.gsub!(/%/, "%%")
Rails.logger.debug(sprintf("\033[32m#{file}#{line}\033[0m\033[1m\033[33m#{text}\033[0m"))
end
You could define it in Kernel (NOT recommended):
module Kernel
def my_log(..)
..
end
end
... if you really want it available anywhere.
Or, place something like this in lib/util.rb:
module Util
def self.my_log(..)
..
end
end
... and make sure to require 'util' in your config/application.rb and then you can call this anywhere:
Util.my_log(..)
why not create an initializer and write this method to the rails module?
# config/initializers.rb
module Rails
def self.log_with_colour(message, level = :debug)
text.to_s.chomp.gsub!(/%/, "%%")
logger.send(level, sprintf("\033[32m#{__FILE__}#{__LINE__}\033[0m\033[1m\033[33m#{message}\033[0m"))
end
end
in your code you can then call Rails.log_with_colour("hello") or Rails.log_with_colour("Hello again", :info)
I put stuff like this in config/initializers/app_methods.rb. They don't need to be scoped inside a class or module. Feels a bit hacky but i never had any problems.
Add it as an instance and class method in Object
class Object
def self.my_log(...)
...
end
def my_log(...)
Object.my_log(...)
end
end