0

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?

3
  • 1
    If you are creating response in the "models", then it is not MVC. It just classical spaghetti code with ORM. Commented Nov 19, 2012 at 19:47
  • Generating HTML at "Model" layer is bad practice, because generating representations is helpers' and views' responsibility Commented Nov 20, 2012 at 8:00
  • This is good to know. I've been researching helpers over the last day, and they seem to have a mixed reputation with rails. Articles like this and this don't cast them in the best light. Perhaps their concerns are overblown, though? Commented Nov 20, 2012 at 16:08

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the feedback! The reason I'm approaching the problem this way is because the list of authors appears in many different views, and I'd rather not repeat the code necessary to create this list. Having a single method would be more efficient and easier to maintain. I'll look more into eval and see if there's a way I can get it to work with my code.
I've placed the code in a partial, but the problem persists. If you look back to the original code, you'll notice the line "authors.to_sentence" at the end. This is why I'm placing the links into an array, and I suspect it's also why I'm getting raw code rather than rendered links. Might there be a better way to implement to_sentence?
I ended up creating an elaborate if-then tree which replicates the effects of to_sentence. It works, but feels awfully hacky. If you don't know of a better solution, I'll go ahead and accept this one in a few hours. This approach works well enough, and your explanations are thorough and clear.
2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.