I have a template that users can upload that generates a report. They can put special tags into the html template and it will replace with data from the db. Quick example:
<div class="customer-info">
<h1>{customer_name}</h1>
<h2>{customer_address_line1}</h2>
<h2>{customer_address_line2}</h2>
<h2>{customer_address_city}, {customer_address_state} {customer_address_zip}</h2>
</div>
I have a controller that looks up the customer and then parses the template and replaces the tokens.
Right now I have the parse code in the controller creating a fat controller. Not good.
But where should I move the code? Model folder? Create a Util folder and put it there?
Just not sure what the Rails Way would be.
I was curious about this too, and found a very similar discussion here. Honestly, I think it depends on how much parse code there is. If there are only a very few lines, then the model is a safe place. If it's going to be a large package, especially a re-usable one, the /lib/ folder may be a better bet for the parsing itself. However, you definitely should remove it from the controller, as you suggested.
I agree that the logic shouldn't be in the controller, but let's get a
little more specific about how you'd go about implementing this.
First, where are you storing your templates in the database? They
should be stored in their own model, let's call it
CustomerTemplate and give an attribute :template of type Text.
So now we have two types of objects, Customers and
CustomerTemplates. How to render a customer given a template? It
honestly wouldn't be terrible to just have a render function in
the CustomerTemplate model that takes a customer and renders it, but
it is putting some logic inside your app that doesn't strictly belong
there. You should separate out the "customer specific rendering logic"
from the "rendering my simple custom template language".
So, let's create a simple template handler for your custom language,
which I'm going to nickname Curly. This handler should know nothing about
customers. All it does is take a string and interpolate values inside
{}'s. This way if you want to add new template types in the future —
say, to render another model like an invoice — you can use the same
template type.
Templates in Rails are classes which respond to call and are
registered with ActionView::Template. The simplest example is Builder.
Here's a quickly written Template handler which renders Curly. The
call function returns a string which is eval'd, so the string has to
be valid ruby code. The string eval in scoped by the render call, so
it has access to any variables passed in via the { locals: {} }
option to render.
# In lib/curly_template_handler.rb
class CurlyTemplateHandler
def self.call(template)
src = template.source
"""
r = '#{src}'.gsub(/{([^}]*)}/) { |s|
local_assigns[$1.to_sym] || s
}
raw r
"""
end
end
Make sure the handler is initialized, and let's set it to listen for
the :curly type.
# In config/initializers/register_curly_template.rb
ActionView::Template.register_template_handler(:curly, CurlyTemplateHandler)
We need to add lib/ to autoload_paths so the class is loaded:
# config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
Finally, we can render our template in our view! I'm embedding the string here, but you'd really get it from a CustomerTemplate object:
<%= render(inline: "<h2>{customer_name}</h2><p>{customer_address}</p>",
type: :curly,
locals: { customer_name: #customer.name,
customer_address: #customer.address }) %>
DO NOT USE MY EXAMPLE CODE IN PRODUCTION! I left out a bunch of corner
cases which you'll need to handle, like sanitizing user input.
Related
First time asking something here on StackOverflow. So excited!
Basically, I'm wondering what the correct design pattern is for what I'm trying to accomplish. I have all my code working but was wondering it there's a more elegant, "RoR Way" to put it all together. For a language/framework so beautiful, it just feels like I've done this wrong:
I have a single master layout page ("WeekSummary") I'm using to display a bunch of "DaySummary" shims. All shims derive from the same template "_day_summary.html.erb."
On WeekSummary, I'm able to pass variables to individual shims fairly easily, eg:
<%= render 'layouts/day_summary', date: '2016-08-12' %>
<%= render 'layouts/day_summary', date: '2016-08-11' %>
But now I'm having trouble invoking a "day_summary" controller definition per each shim. Essentially, at this point in the render lifecycle, I believe I've already passed through the "C" part when the RoR engine called my "week_summary" definition (in which I did hold some business logic). But now I want the RoR engine to go back to the controller and call a "day_summary" controller definition per each shim I've defined on WeekSummary view page. I would like all variables/definitions to be then locally scoped to each shim (so I can reuse the same var names, etc).
I wasn't able to figure out how to do that though so right now I've simply dumped all my shim-specific business logic at the top of the "_day_summary.html.erb" in a massive <% %> block. Having so much business logic there in a View shim seems wrong though.
I hope this made sense. Does anyone have any suggestions for how to properly do this? Essentially, I'm trying to encapsulate the rendering of each shim into its own MVC lifecycle/pattern, if that makes sense. Thank you!
Edit: In response to kcdragon's code request on what's happening inside each shim:
So, for example, for each day_summary shim, I wish to calculate that day's pnl.
At the top level, in the week_summary controller def, I get all transactions:
#transactions = Transaction.all.order('ticker', 'date DESC')
Then in each shim, I filter #transactions by only the date I care about for that shim. Thus, a sample of each shim's business logic includes the below-- in this example, calculating that day's PnL:
transactions = #transactions.where(date: '2016-08-08')
pnlHash = Hash.new
totalPnl = 0
transactions.each do |t|
if !pnlHash.key?(t.ticker)
pnlHash[t.ticker] = t.pnl
else
pnlHash[t.ticker] += t.pnl
end
totalPnl += t.pnl
end
<%= totalPnl %> is then rendered elsewhere on the shim.
There's other business logic too that happens in the shim, but this is a good representative sample.
Now, obviously at the top level (week_summary), I could "pre-process" all daily PnL calculations and then store them in some massive hashtable which I'd then use to extract values per day_summary shim. (In this example, I guess that'd be a true model of the week_summary view.) But I don't want to do that. I want to encapsulate day_summary into its own thing, where all its business logic and rendering is processed on the fly as week_summary renders. Hopefully that makes sense?
Edit2: For sake of clarity, here's what each day_summary shim looks like. On the week_summary view, five of these guys are rendered, each one corresponding to its respective date:
Here's what the day_summary shim looks like.
I'm wondering if there's a specific programming principle (Demeter?) that supports the idea that Rails helpers should never use controller instance variables, rather, they should receive such variables as function parameters. For example, assume my ChickensController#squawk action creates an instance variable called #egg. Furthermore, assume the squawk view contains a call to a helper called cockadoodledoo, implemented like so:
def cockadoodledoo
#egg.to_s
end
Would it be better or unnecessarily verbose to pass #egg as a parameter, such that the view calls cockadoodledoo(#egg) and for the helper to resemble:
def cockadoodledoo(egg)
egg.to_s
end
I hope one of you happy hackers is bored enough on a Friday afternoon to assert an answer. Cockadoodledoo!
This question here is similar, but was never accurately answered.
Receive them as a param. Otherwise, as the app grows, it gets very difficult to trace where the instance vars are being set when refactoring, troubleshooting, etc.
Also, I believe there's a general best practice to only use instance vars in views within the initial template...and from there you should pass the var into helpers and other partials.
I'd say you should always pass the variables explicitly to your helper for 2 reasons:
you control exactly what you do
above all, you can test your helper
I don't know if there is any named principle governing this sort of thing but I would pass an argument. Not only will the argument make your helper easier to test and your application's data flow easier to follow but it will also let you use one helper for a single instance as well as a list; if you pass an argument then both:
<%= cockadoodledoo #egg %>
and:
<% #eggs.each do |egg| %>
<%= cockadoodledoo egg %>
<% end %>
will work as expected without introducing a special cockadoodledoo that handles a list in #eggs rather than a single #egg.
Since helper messages are mixed in to all controllers, hence available to all views (including partials and layouts), it's always wise to establish a clear contract - the parameters.
The only exception I could think of is when a instance variable is also available to all views and controllers, like a menu or something similar.
The case is simple: I have markdown in my database, and want it parsed on output(*).
#post.body is mapped to the posts.body column in the database. Simple, default Activerecord ORM. That column stores the markdown text a user inserts.
Now, I see four ways to offer the markdown rendered version to my views:
First, in app/models/post.rb:
# ...
def body
markdown = RDiscount.new(body)
markdown.to_html
end
Allowing me to simply call #post.body and get an already rendered version. I do see lots of potential problems with that, e.g. on edit the textfield being pre-filled with the rendered HMTL instead of the markdown code.
Second option would be a new attribute in the form of a method
In app/models/post.rb:
# ...
def body_mardownified
markdown = RDiscount.new(body)
markdown.to_html
end
Seems cleanest to me.
Or, third in a helper in app/helpers/application_helper.rb
def markdownify(string)
markdown = RDiscount.new(string)
markdown.to_html
end
Which is used in the view, instead of <%= body %>, <%= mardownify(body) %>.
The fourth way, would be to parse this in the PostsController.
def index
#posts = Post.find(:all)
#posts.each do |p|
p.body = RDiscount.new(string).to_html
#rendered_posts << p
end
end
I am not too familiar with Rails 3 proper method and attribute architecture. How should I go with this? Is there a fifth option? Should I be aware of gotchas, pitfalls or performance issues with one or another of these options?
(*) In future, potentially updated with a database caching layer, or even special columns for rendered versions. But that is beyond the point, merely pointing out, so to avoid discussion on filter-on-output versus filter-on-input :).
The first option you've described won't work as-is. It will cause an infinite loop because when you call RDiscount.new(body) it will use the body method you've just defined to pass into RDiscount (which in turn will call itself again, and again, and so on). If you want to do it this way, you'd need to use RDiscount.new(read_attribute('body')) instead.
Apart from this fact, I think the first option would be confusing for someone new looking at your app as it would not be instantly clear when they see in your view #post.body that this is in fact a modified version of the body.
Personally, I'd go for the second or third options. If you're going to provide it from the model, having a method which describes what it's doing to the body will make it very obvious to anyone else what is going on. If the html version of body will only ever be used in views or mailers (which would be logical), I'd argue that it makes more sense to have the logic in a helper as it seems like the more logical place to have a method that outputs html.
Do not put it in the controller as in your fourth idea, it's really not the right place for it.
Yet another way would be extending the String class with a to_markdown method. This has the benefit of working on any string anywhere in your application
class String
def to_markdown
RDiscount.new(self)
end
end
#post.body.to_markdown
normal bold italic
If you were using HAML, for example in app/views/posts/show.html.haml
:markdown
= #post.body
http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#markdown-filter
How about a reader for body that accepts a parse_with parameter?
def body(parse_with=nil)
b = read_attribute('body')
case parse_with
when :markdown then RDiscount.new(b)
when :escape then CGI.escape(b)
else b
end
end
This way, a regular call to body will function as it used to, and you can pass a parameter to specify what to render with:
#post.body
normal **bold** *italic*
#post.body(:markdown)
normal bold italic
In Django, there's the existence of a message framework which notifies the user after an action is performed. For instance, from the views.py there might something like:
if success:
messages.success(request, 'Update Successful')
else:
messages.warning(request, 'Something is missing')
I believe Rails have something similar with:
flash[:notice] = 'Something is missing'
Should the messages above be hard-coded in the controller?
If I understand your question, you are asking whether or not you should hard-code a string value into your code. In compiled languages, you often use a reference to a string, instead of entering the actual string. ala:
message.success(request, message_resource.success )
This gives you the freedom to change the string value without re-compiling the code, and has a performance benefit in some instances.
Because Python is dynamic this really isn't required, but depending on the size of the project, may be beneficial.
Imagine a situation where the software is used by people speaking different languages, you could detect the required language somewhere else in your code and initialize message_resource.success, as well as any other strings, to be in said language.
here is a simple example:
german.py
# german language messages
success = "Sie folgten!"
failure = "Sie fallen aus!"
english.py
#english language messages
success = "You succeeded!"
failure = "You fail!"
main.py
# main
# import english language
message_resource = __import__('english');
print message_resource.success
print message_resource.failure
# import german language
message_resource = __import__('german');
print message_resource.success
print message_resource.failure
In Rails any marshallable object can be put in the flash.
Therefore it is better to do it in the view.
<% if flash[:notices] && flash[:notices][:missing] %>
<div><%= t("Somethign missing") %></div>
<% end %>
Putting text and translations in the controllers is indeed a bit ugly..
Messages are events. Things that happen. Which is what the "controller" part of MVC is all about. The "how".
(Django calls this "view functions".)
The model is (mostly) stuff that's static, final, persistent. Passive. The "what".
Things happen to the model. Things are initiated by the controller.
Messages come from the controller for presentation to the person.
It's possible that a model's method might need to provide some evidence or information on a state change. This is not an example of a message being created by the model. If the model has methods that do mutation/update/state change, then you have to break things into two pieces.
The "controller" (i.e., Django view function) must use the model's API to make the state change and collect any information on that state change.
The "controller" (the view function) does I18N translation and presents the message.
generic examples
Model: The method is_missing() would go in the model, if it is dependent only on the data.
Controller: Marshalling data from the model for the view: missing = Suff.get_by_id(1).is_missing()
View: <span>{$missing}<span>
But exactly where you draw those lines is always up for debate. In your example, I would say flash, success, and warning are over-stepping their bounds on how to present the data and would be better in the view since they are generic data presenters.
I'm writing a webapp in Ruby on Rails 3. Rails 3 automatically escapes any potentially-bad strings, which is generally a good thing, but means if you assemble HTML yourself, you have to call html_safe on it.
I have a Card model, which has several text fields, the contents of which are not trusted (may contain evil HTML or script). I have a function which performs a few transforms on one of these text fields, using other knowledge about the specific Card, to produce HTML output. I want to embed the HTML produced by this function in several places throughout several parts of my app.
Conceptually, this helper is to do with the View. However, I can't find any way to write functions in my View files; it seems they have to go in Helpers or the Controller/Model.
Since this function is very much specific to a Card object, the next best option would be to have a function inside my Card model card.rb:
class Card < ActiveRecord::Base
[...]
def format(unsafe_text)
initial_text = h unsafe_text # aka html_escape unsafe_text
# assembles HTML output based on initial_text and fields of self
output_text.html_safe!
end
Then I'd like to call this in assorted views by doing things like:
Rules text: <%= format(#card.rulestext) %>
However, there's a big problem here as well. In the Card model card.rb, I am able to use the html_safe! function, but I'm not able to use h or html_escape. It seems that the h and html_escape functions are only available in ERB views, not in the helpers or controllers!
There are a few workarounds. I can make format not sanitize its input, and go
Rules text: <%= format(h(#card.rulestext)) %>
But that's both prone to dangerous slipups (one missing h() and we've got problems) and is very non-DRY. At the moment I'm using a partial to gain access to the h() function:
(in a normal view)
Rules text: <%= render 'formattext', :text=> #card.rulestext %>
(app/views/shared/_formattext.html.erb)
<%= #card.format(html_escape(text)) %>
But this still feels dangerous. All I have to do is make single forgetful call to format(sometext) in a view, rather than calling render 'formattext', :text=> sometext, and I've got unescaped text running around.
Is there any better way to do this? Is there a way to write helper functions to live in the View rather than the Model or the Controller?
Place the logic that does your view assembly into a CardHelper:
app/helpers/card_helper.rb
class CardHelper
def rules(card)
initial_text = h card.rules_text
# assembles HTML output based on initial_text and fields of card
output_text.html_safe
end
end
It's not clear from your example whether you want to format several fields via the format method. If that's the case, then you might be able to do:
class CardHelper
def format(card, attribute)
initial_text = h card[attribute]
# assembles HTML output based on initial_text and fields of card
output_text.html_safe
end
end
You can use this helper like any other:
class CardsController
helper CardHelper
end
and in your views:
<%= rules(#card) %>
or
<%= format(#card, :rules) %>
Escaping the content for view is a View responsibility, this is the reason why the h helper is not available in controllers or models.
Still, I don't understand why can't you simply sanitize the content in the view.
Also note that, in Rails 3, you don't need to call the h helper.
Content is sanitized automatically by default unless you flag it as html_safe!.
The main reason why is not logically true to use the h helper in the model is because the model should work view-independently. In other words, the model should not care whether the content is going to be embedded in a HTML document or JSON file (which requires a different escaping approach compared to HTML).