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.
Related
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 %>
I have a fee column in my model and it is an integer type, so I try to create a tiny helper to add a dollar sign neatly in front. Which means, instead of writing:
span = "$#{#object.fee}"
I can write something like
span = #object.fee.dollar
So I created the tiny helper.
module ApplicationHelper
def self.dollar
"$#{self.try(:to_s)}"
end
end
I am not sure where to put it, so basically it's now showing
undefined method `dollar' for 180:Fixnum
number_to_currency()
Rails 4.2 has this ActionView::Helper
number_to_currency(1234567890.506)
Helper
If you want to implement this as a helper, this works
module ApplicationHelper
def dollar(amount)
amount = number_to_currency(amount)
end
end
Invoke
<%= dollar(your_var_here) %>
Rails spec for number_to_currency()
http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_currency
Note: Other versions of Rails may have this function, you'd have to check your version.
module ApplicationHelper
def dollar(amount)
"$#{amount}"
end
end
and then:
span = dollar #object.fee
I think it's because you're in a helper, so you can't refer to self.
You can do it in your Model, or in the helper do :
def print_dollar(your_value)
Or, you can also use : number_to_currency(dollar, :unit => "$"), which will render it the way you want.
Hope it help
Your helpers are included in the view context, so you need two changes:
def dollar - because it's included in the renderer, you don't need self
Call it as dollar(#object.fee) - it's not included on the object, but in your view. If you want to call it as #object.dollar, declare the method in whatever class #object is.
Additionally, the number_to_currency helper already exists and is quite robust. Perhaps you want to use that.
I'm trying to implement Decorators using the learnings from "Rails 4 Patterns" Code School course, but I'm running into trouble as I need a view helper in the Decorator class.
I want my view to have:
<%= #model_decorator.previous %>
Then in the decorator:
def previous
if object.prev_item.nil?
"Previous"
else
link_to("Previous", object)
end
end
The course suggests you make a call to the decorator within your view helper in the view file itself, but that's no good if the logic could output one result with a helper and one without. (i.e. need the output to be a link or not).
I've tried using helpers.link_to but it errors out as not providing the correct information for the url_for option. I've confirmed link_to("Previous", object) works within the view itself.
For Rails 4
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
As for me it`s better to make a decorator for it:
class LinkDecorator
include ActionView::Helpers::UrlHelper
def initialize(label, object)
#label = label
#object = object
end
def show
link_to(label, url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
end
def index
link_to(label, url_helpers.send("#{object.class.name}s_path".to_sym))
end
...
private
attr_reader :label, :object
def url_helpers
Rails.application.routes.url_helpers
end
end
Example usage:
LinkDecorator.new(object.name, object).show
If I understand your problem correctly, you essentially want links in a plain old ruby object.
My solution would be this:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.objects_path(object))
# assuming the object is always of one class
If the object is of a different class, than it would be possible to use the .send method to send the correct message to app ie.:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class}s_path".downcase.to_sym, object))
# I'd create a function out of that line to make it a bit neater
It sounds like the error thrown by url_for comes from missing the routes and there's a few ways to include those. My solution kinda avoids that problem by using Rails.application.routes.url_helpers. Hope this helps!
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've tried Facebook's Open Graph protocol in adding meta data on Rails pages. What I want to do now is to make my code not duplicated or D.R.Y.---instead of putting one meta-data header for each controller page I have, I'd like to create a base class called "MyMetaBuilder" which will be inherited by the sub-pages, but don't know where and how to start coding it...
Someone suggested that meta data property values must be dynamically generated depending on the context. For example, PlayMetaBuilder, CookMetaBuilder and so on...
Also, when unit testing the controller action, how do I verify for its existence?
Thanks a lot.
One thing is defining the tags, another is rendering them. I would do the following:
write a controller mixin (something like acts_as_metatagable) where I would define specific fields for each controller (and populate the remaining with defaults). These would be assigned to a class (or instance) variable and in this way be made accessible in the rendering step).
write an helper function which would take all my tags and turn them into html. This helper function would then be called in the layout and be rendered in the head of the document.
so, it would look a bit like this:
# homepage_controller.rb
class HomepageController < ActionController::Base
# option 1.2: include it directly here with the line below
# include ActsAsMetatagable
acts_as_metatagable :title => "Title", :url => homepage_url
end
# lib/acts_as_metatagable.rb
module ActsAsMetatagable
module MetatagableMethods
#option 2.2: insert og_tags method here and declare it as helper method
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
def self.included(base)
base.helper_method :og_tags
end
end
def acts_as_metagabable(*args)
include MetatagableMethods
# insert dirty work here
end
end
# option 1.1: include it in an initializer
# initializers/acts_as_metatagable.rb
ActiveController::Base.send :include, ActsAsMetatagable
# option 2.1: insert og_metatags helper method in an helper
module ApplicationHelper
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
end
What I did for Scoutzie, was put all metadata into a head partial, with if/else cases as such:
%meta{:type => 'Author', :content => "Kirill Zubovsky"}
%meta{'property' => "og:site_name", :content=>"Scoutzie"}
-if #designer
...
-elsif #design
...
-else
...
This way, depending on the variables that load, I know which page it is, and thereby know which metadata to include. This might not be an elegant solution, but it works and it's really simple.