Rails: Creating HTML in a Model - html

I have a Publication model with a has_many relationship to Contributors. In the model, I have a method that's meant to create an html-ready by line:
def authors
authors = []
Contributor.where(:publication_id => self.id).each do |author|
authors << "link_to "+author.name+", Contributor.find("+author.id.to_s+")"
end
authors.to_sentence
end
In my view, I have the following line:
by <%= #publication.authors %>
But instead of rendering links, it renders the raw code, such as:
by link_to B, Contributor.find(1)
I've tried patching this by adding .html_safe to the end of #publication.authors, but to no avail. Is there a better way to transfer these links from the model to the view?

You're pushing strings into your authors array. It looks like valid code, so running eval on it should work. (Actually author.name will probably evaluate as an undefined symbol, so scratch that.)
A better way would be to use a has_many :authors, :model => 'Contributor' relationship on your Publication model, and you can bring up your array of Contributor objects by simply calling
#publication.authors
You'd want to iterate over these in your view like so:
<% #publication.authors.each do |author| %>
<%= link_to author.name, author %>
<% end %>
Note also that if you're displaying multiple Publication objects in a view this way, you'll want to use Publication.includes(:authors) in your controller when you're retrieving them to avoid the "N+1" problem.
Now, three lines of code doesn't seem very expensive to repeat, but there are ways to DRY that without violating the MVC pattern and cluttering your model:
Place the code to print a publication's authors into a partial, and call the partial as needed.
Place the code into a helper, include the helper and call the method as needed.
Here's a snippet from the source for to_sentence (you can adapt it for your needs, I think):
case length
when 0
""
when 1
self[0].to_s.dup
when 2
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
The full source can be found here.

It looks like you are trying to use haml syntax in your line. Maybe instead of using link_to, use an html hyperlink tag itself?
That being said, why are you having a model return html?
Edit: bdares answered already with what I was trying to say

Related

rails exclude for multiple strings

I have the following code. This piece of code is working for me.But it looks ugly. is there any cleaner approach for achieving this?
<% if request.original_url.exclude?("/invest/") || request.original_url.exclude?("/study/") || request.original_url.exclude?("/work/") %>
some html tags here ...
<% end %>
Actually exclude? is defined in Rails is defined just as the opposite of include?, which would be very good if it's able to receive a regular expression, but it doesn't.
What you can do is to store in an array the substrings you want to check against the request.original_url, and use any? to check if any of those return true, which helps you to save a bit of characters, but would be good if that list increases.
You can try something like this:
['/invest/', '/study/', '/work/'].any? { |e| request.original_url.exclude?(e) }
First of all you should use helper and add following codes in it
exclude_ursl(request)
url_rules = ["/invest/", "/study/", "/work/"]
url_rules.each_with_object([]) do |url,urls|
urls << (request.original_url.exclude?(url))
end.include?true
end
Now use it in your view
<%if exclude_urls(request) %>
I don't have current such environment to test, so you can test it.

How convert 'link_to' to Absolute in Rails

I've a problem in my program, I add in DB a link, for example, "www.google.com" and when I clic in the link I'm redirected to localhost:3000/www.google.com, this doesn't happen when I put "http://www.google.com" in DB.
My code
<td><%= link_to t.title, t.link_to_idea, :target => "_blank" %></td>
How do I make to convert this link always in absolute? (I think I this's solution)
Thanks!!
You could do something like:
<td><%= link_to t.title, t.link_to_idea.start_with?('http') ? t.link_to_idea : "http://#{t.link_to_idea}", :target => "_blank" %></td>
..but that assumes you want all links to save with http and not https.
You're probably better off checking for the protocol before you save the link in the DB.
For example, you could do what this answer suggests: Add http(s) to URL if it's not there?
before_validation :smart_add_url_protocol
protected
def smart_add_url_protocol
unless self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
self.url = "http://#{self.url}"
end
end
That way you can just do what you already have.
I think your best bet is to update the links in your database to all conform to a standard format. You can also add a more basic validation to make sure all links match a valid format:
validates :link_to_idea, format: URI.regexp
You can also run a backfill on your database that checks old links to make sure they match this pattern and then update the ones that do not work. Are you using MySQL?
Either way, the best answer is not to try to make your app render any-old-thing that the user puts in, but to clean the data before it gets into the database.
If you can't control what goes into the database, then I would simply render, as text, anything that doesn't match that Regexp and let users put that into their browser on their own.
I'd suggest that you create a decorator using Draper. This will allow you to decouple presentation logic from your domain object.
Once you've set it up then you could write something similar to this:
# app/decorators/idea_decorator.rb
class IdeaDecorator < Draper::Decorator
delegate_all
def idea_url(protocol = 'https')
return link_to_idea if has_scheme?
"#{protocol}://#{link_to_idea}"
end
private
def has_scheme?
# .. some method here to determine if the URL has a protocol
end
end
And used in the view:
<%= link_to t.title, t.decorate.idea_url('https') %>

Using one '_form.html.erb' partial but have different elements appear for certain views

Is it possible to display certain elements within a partial for a particular view in Rails? For example I'd like the submit button's text to change depending on the view: so if I'm in the new.html.erb I'd like the submit button to appear as, <%= f.submit 'Create Account' %> and <%= f.submit 'Update Account' %> for edit.html.erb. The unconventional way would be to manually add the custom code into the each view but is there a clever way to do this in my _form.html.erb partial?
First of all, I would recommend putting it into the new and edit views. However, you can switch off of params[:action] if you want to. As in
<%= f.submit(((params[:action] == 'new') ? 'Create' : 'Update') + ' Account') %>
Use simple_form with i18n for that. SimpleForm make it automatically.
Example:
<%= simple_form_for(#message) do |f| %>
<%= f.error_notification %>
<%= f.input :title %>
<%= f.input :description %>
<%= f.button :submit, class: "btn btn-primary" %>
<% end %>
I second kdeisz's answer if your intention is to use a single partial. The line he wrote will not be necessary if you use two separate views - You can just use different names on the same button in each view without any need for conditional logic.
To answer your supplemental questions: There is a tradeoff here between future changeability and DRY code. If your new and edit needs will start to differ significantly, you will have a lot of bloated, difficult-to-change conditional logic in your partial if you use it to render major features.
If you keep the views separated, this may repeat a significant amount of code, but it will also make the individual pages easier to change; the functions of each view will be tailored very specifically to the needs of each HTTP verb.
The answer isn't to conform completely to REST or to DRY "just because", but to ask yourself what will result in more work down the road. If your new and edit pages will be basically the same but for a few very minor features, the single partial (DRY) is more practical. If you see them diverging significantly in the future, keep them separated into two views (less DRY but more changeable).
Params. Each request made to Rails will automatically include an action and a controller based on the route the user requests; for example, navigating to /foo/bar might trigger action bar for controller foo, depending on how you've set up config/routes.rb. Rails fills in params[:action] and params[:controller] with these automatically. A good explanation of how this works, and how to access path and request params, can be found here.

Using Namespace nested resources with MongoMapper EmbeddedDocument with form_for

I'm submitting a form that is sending in a child resource's id value for both the child resource and it's parent resource's id. I don't know how to get the form submission to stop duplicating the child id as the parent resource's id.
Technology Stack:
Rails 3.2.8
MongoMapper
Simple Form (although the same issues occur with the stock form_for that it wraps)
Relevant excerpt from routes.rb:
namespace :mock do
resources :patients do
resources :allergies
end
end
My models are defined in Mock::Patient and Mock::Patient::Allergy.
In my Allergy resource's view partial _form.html.erb, I have the following opening form helper usage (I use Simple Form, but the same results occur with the stock form_for helper):
<%= simple_form_for [#mock_patient, #mock_allergy], :url => mock_patient_allergy_path(#mock_allergy), do |f| %>
While this renders, it is submitting to this route:
/mock/patients/:patient_id/allergies/:id
So my allergies_controller.rb file does receive an update action (in the case of an edit operation).
However, when I look at the params, params[:patient_id] is the same as params[:id]. Both are actually the id value of the specific nested allergy resource being edited. By the parent resource (Patient in this case) has its context lost.
So, I set out to include a hidden field in the form:
<%= hidden_field_tag('patient_id', #mock_patient.id) if #mock_patient %>
When I view the page source before submitting the form, sure enough, I can see the correct patient_id value.
There seems to be some built in form handling logic that is replacing the parent resource's id with that of the child resource.
My model files, using Mongo Mapper are:
class Mock::Patient
include MongoMapper::Document
# other Patient model keys here
many :allergies, :class => Mock::Patient::Allergy
end
class Mock::Patient::Allergy
include MongoMapper::EmbeddedDocument
# other Allergy model keys here
belongs_to :patient, :class => Mock::Patient
end
To recap, I'm able to finagle the form tag to submit to the correct route, but the params hash received by the controlled is jacked up - losing the parent resource context.
Other variations of my form tag that I've tried, to no avail:
<%= simple_form_for #mock_allergy, :url => mock_patient_allergy_path(#mock_allergy), do |f| %>
and
<%= simple_form_for #mock_allergy, :html => { :class => 'form-horizontal' } do |f| %>
In the above both cases, the basic route template generated is fine, and it reaches my allergies_controller, but when I go to inspect params[:patient_id], I get an incorrect value. It is actually a duplicate of params[:id].
Concrete Example
My form tag line:
<%= simple_form_for #mock_allergy, :url => mock_patient_allergy_path(#mock_patient, #mock_allergy) do |f| %>
This generates a page that when I view source, reports:
<form accept-charset="UTF-8"
action="/mock/patients/5092c815fdb5424df800000d/allergies/5092c815fdb5424df800001c"
class="simple_form form-horizontal"
id="edit_mock_patient_allergy_5092c815fdb5424df800001c" method="post"
novalidate="novalidate">
But when it hits my controller, who looks to find a Mock::Patient based on the on params[:patient_id], it doesn't get the '5092c815fdb5424df800000d' value I would expect. On my browser, the error page indicates the parameters received were:
{ "patient_id"=>"5092c815fdb5424df800001c",
"id"=>"5092c815fdb5424df800001c" }
These two IDs are identical, and therein lies the problem.
Suggestions?
Solved:
In my update method, on success the redirect from scaffolding was:
redirect_to #mock_allergy
instead of:
redirect_to mock_patient_allergy_path(#mock_patient, #mock_allergy)
So, I was getting punted to a correctly formed route, but with a bogus patient_id.
My other mistake, was that in modeling Allergy, I should have used an embedded_in :patient instead of the belongs_to :patient line.
Derived Question:
Why is it, that #mock_allergy, and instance of Mock::Patient::Allergy, knowing it is a nested resource, couldn't generate a correct path that included the right patient_id?

Removing header rendering in specific rails .html.erb file

I'm rendering a header universally across my application in layouts/application.html.erb.
I'd like to make the header not appear in a specific foo.html.erb file.
What's the syntax for un-rendering a universal layout?
EDIT:
The controller for the layout is a devise controller, specifically Sessions Controller.
in your controller you can set the layout to false (or another layout), if false then you need all of the html,head,body tags in your view file
class BarController < ApplicationController
def foo
render :layout => false # render foo.html.erb with no layout
end
end
see section 2.2.11.2 from the rails guides: http://guides.rubyonrails.org/layouts_and_rendering.html
EDIT: including devise layout overrides
in config/initializers/devise.rb
Devise::SessionsController.layout "bar"
Devise::RegistrationsController.layout "foo"
Devise::ConfirmationsController.layout false # never tried this, guessing it would work
Devise::UnlocksController.layout "bar"
Devise::PasswordsController.layout "foo"
also see the wiki post - https://github.com/plataformatec/devise/wiki/How-To:-Create-custom-layouts has at least one other way
Let's say the controller and action that renders the template foo.html.erb is 'things#foo' and the path to this action is things_path. You can wrap the header in conditional tags as follows
<% unless request.path == things_path %>
<% end %>
. There are several ways to achieve this, but here is one.