Return html from method - ruby-on-rails

I'm attempting to return HTML straight from a model method to show the last reply to a Topic.
My Topic model:
class Topic < ActiveRecord::Base
has_many :replies
def last_reply
self.replies.last.name
end
end
and the view:
<%= topic.last_reply %>
It renders HTML with speech marks around it indicating a string. How do I get rid of these speech marks?
My thought:
def last_reply
self.replies.last.name.html_safe
end
I'm scared of doing this incase someone has embedded JavaScript or something as a reply name. I have validations to stop this, but I still want to be doubly-safe. If embedded JavaScript was to be displayed, I would obviously want it literally displayed on the page and not processed by the browser. Does html_safe do this?
Should I be doing this in a decorator or helper? I feel ERB tags should never have 'programming' in them as they should simply contain one method call to display information:
view:
<%= decorate_last_reply %>
decorator:
def decorate_last_reply
model.last_reply #=> "Yep a string I am"
end
I still want to know how I should be returning HTML without those speech-marks. I could probably use the .gsub() method to get rid of them, but I want to know how to do it properly.

Logically topic.last_reply should return the last record - an object. You could do that by scoping or method. Then I would pass it to helper or just create a helper topic_last_reply with getting topic.last_reply and partial rendering

Related

Semi-global Rails partial

Is there a better way to achieve what I'm going for?
I have a partial in the /views/shared/ folder that has all the fields that are in a form being used to send an email.
A helper method with default options to render said partial (render partial: 'shared/email_fields' locals: locals where locals is a hash of default variables).
A helper method for every form sending an email that calls the above helper method and passes in either a FormBuilder object or a string containing the beginning of the name html attribute.
The problem I'm having: Most of the email forms differ slightly which results in me having to add additional options to the locals hash and I feel like the global partial is becoming bloated. Is there some way of using a global partial in this way such that the partial doesn't become super bloated?
I've thought of having each form completely separate but that's bad for upkeep and DRY. I've thought of passing in the name of a partial to be rendered inside the global partial but some of these forms need the same options and are rendered from different controllers and I wouldn't want to put a bunch of partials that aren't global in the /views/shared/ folder. Right now, I'm just sticking with the bloated global partial.
Any help would be appreciated!
Here's how I do it. This is going to sound weird, but bear with me.
So, I have basically two forms in my applications. For a form that submits via javascript, it looks like this:
#views/shared/_remote_form.html.haml
= form_tag #presenter.form_path,
remote: true,
id: #presenter.form_id,
class: #presenter.form_classes,
data: #presenter.form_data,
method: #presenter.form_method do
.well
= #presenter.form_inner
.form-controls-container
.form-controls-wrapper
= #presenter.form_controls
As you can see, I use presenters. The presenters are instantiated in the relevant controller as a controller variable, so that the presenter is available to the partial. Something like:
class FooController < ApplicationController
def new
#presenter = NewFooFormPresenter.new(self)
render partial: 'shared/remote_form'
end
...
end
You can see that I'm passing in the controller so that the presenter is able to render various parts of the form.
All FormPresenters inherit from FormPresenterBase that has stubbed methods for each of the methods called in the form. Something like this:
class FormPresenterBase
def initialize(controller)
#controller = controller
end
def form_path
root_path
end
def form_id
'bogus-form-id'
end
def form_classes
'something-bogus'
end
def form_inner; end
def form_controls; end
...
end
That let's me bootstrap the form without throwing a bunch of errors all the time. Naturally, that stubbed form won't really work, but that's okay because each FormPresenter will override the stubbed methods with real values. So, something like:
class NewFooFormPresenter < FormPresenterBase
def form_path
new_for_form_path
end
def form_id
'new-foo-form'
end
def form_classes
'something-not-bogus'
end
# The form fields could be unique to this form. Or, I might have a set of common
# fields that I use across multiple forms. I just decide which partial has the
# correct set of fields and render it here.
def form_inner
render partial: 'new_inner_fields'
end
# The controls are also rendered from partials. Here, I want to have an okay
# button and a cancel button. So, I just call the correct partial that
# renders those. I call html_safe on the resultant string so that it renders
# correctly.
def form_controls
[:okay, :cancel].each_with_object("") do |control_sym, to_return|
render partial: "shared/form_widgets/#{control_sym.to_s}_button"
end.html_safe
end
...
end
Of course, I can get tricky with my FormPresenters. If there are families that share common methods, I can either use further inheritance or module inclusion to keep everything DRY.
So, once I have all my basic form widgets (field combinations, controls, etc.) configured as partials, I can just mix and match in my presenter to my heart's delight. And (at least for forms), I basically never have to write another partial for the rest of my life. Whenever I need a new variant, I just spin up a new FormPresenter and customize it to give me the form I desire.
Actually, there's a little bit more to it than all of that, but hopefully this gives you a sense of another way to skin the cat.
An approach is to have a separate partial for each form. Take all of the items the forms have in common and put them in a partial. You can then reference the "common items" partial within your individual form partials. Depending on how your forms are structured, you may have several "common items" partials, but that is okay. The goal is to keep the code organized and DRY.

How to compare two items within Ruby on Rails?

So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id

ActiveSupport::SafeBuffer not rendered in view?

Very simple question really, but it's driving me nuts.
I have this method call in a Rails view:
<%= get_image(#document) %>
The method in here returns an object of type ActiveSupport::SafeBuffer. If I call .to_str on it in a console, I see the expected result (just an html img tag). However, when I stick the above code in a view, there's nothing there. Just an empty string.
If I do this:
<%= get_image(#document).to_str %>
in an effort to convert the SafeBuffer into a String object, I see the literal HTML in the page, and not the image tag.
I feel like I'm missing something simple or elementary here. Can anyone point the way? Thanks in advance!
EDIT: since it's been requested, here's what the get_image method looks like:
def get_image(document)
document[document.type+'.image'].nil? ? nil : document[document.type+'.image'].as_html_safe
end

Mailboxer: modifying how inbox renders - rails

I'm using a gem called mailboxer to allow users to send messages between each other.
In my controller:
def mailbox
#mailbox ||= current_user.mailbox
end
In my view I have:
<%= render mailbox.inbox %>
Which renders each conversation subject under a bullet list with a link to trash the conversation. How do I style this or modify how it renders? I can't seem to find the code anywhere that let's me modify how this renders.
mailbox.inbox is just an array of conversation objects. The magic here comes from the way that Rails automatically renders a partial for each object in a collection when an array is passed to render. You can modify how the collection is rendered by creating your own partial that does whatever you want to do with that array of conversations. This describes it further:
https://github.com/RKushnir/mailboxer-app/issues/2
And this part of the Rails guide describes this behavior (might help when you are figuring out how to customize - see section 3.4.5):
http://guides.rubyonrails.org/layouts_and_rendering.html#using-render

Wrap images from text field into image_tag

I would like to create a helper or change the model.rb to behave like:
Post model has a text field, which contains urls, all separated with new lines.
On create or update, rails scans this field, when it finds url like:
http://i.imgur.com/imanimage.png
http://i.imgur.com/anotherimage.png
Then it leaves them as is, but in the show action it renders them as:
= image_tag('http://i.imgur.com/imanimage.png', class: 'my-image-class')
= image_tag('http://i.imgur.com/anotherimage.png', class: 'my-image-class')
Probably a helper method could do this.
Does the text field store anything else but the urls? If it does, you should consider creating helper using regex to get all urls and change these to <img src=''/>,
If not, you can use in haml/erb file.
Model.text.each_line do |m|
image_tag(m, class: 'my_img')
end
Reference: http://ruby-doc.org/core-2.1.0/String.html#method-i-lines
If you're saving these URLs in your database, you it should be quite a simple procedure:
#app/models/image_url.rb
Class ImageURL < ActiveRecord::Base
end
#app/controllers/image_urls_controller.rb
def show
#url = ImageURL.find(params[:id])
end
#app/views/image_urls/show.html.erb
<%= image_tag(#url, class: 'my-image-class')
If you add some more context to your question, I'll be able to give you a more refined answer!

Resources