Using Rails polymorphism for nested comments - ruby-on-rails

I need to build a nested comments system in a Rails 3 application that allows for comments on many models (articles, posts, etc) and am debating rolling my own solution along the lines of this post. There are gems available like acts_as_commentable_with_threading with awesome_nested_set, but they feel bloated for my needs.
I need to be able to add comments to multiple models
I need to be able to add comments to comments, infinitely deep
I need to be able to efficiently retrieve all descendants for a post, article, etc
I need to be able to efficiently present the comments in their appropriate nesting
My question is, were I to roll my own solution what potential hiccups I could face. I want to avoid going down one path only to reach a dead end. My initial concerns relate to efficiently querying for children. Say, for instance, getting a list of an articles descendant comments (children and children of children).
Anyone have input on this? Thanks.

There are two kinds of nesting you can do: a tree and a nested set.
acts_as_tree stores only a parent_id and so it is really fast to write new entries, but you have to recursively walk the chain of id numbers to get a list of all the children. This is not a good choice when you need to do lots of reads.
awesome_nested_set records three bits of information: parent_id, lft and rgt. The left and right values are calculated so that they contain all the children ids for that entry. This is very fast for read operations but slower to write.
In your case I think awesome_nested_set is more appropriate. You might think it seems overkill, but nested sets get complicated in a hurry. You need to use the nested set pattern to efficiently query children.
You only need to use two methods to render the entire tree of comments: iterate over Comment.roots and for each comment, render comment.children.
class ModelController < ApplicationController
def show
#model = Model.find_by_id(params[:id])
#comments = #model.comments.roots
end
end
<ul id="comments">
<% #comments.each do |comment| %>
<%= render :partial => 'comment', :object => comment %>
<% end %>
</ul>
<!-- _comment partial -->
<li class="comment">
<!-- comment markup -->
<% if comment.children.present? %>
<ul>
<%= render :partial => 'comment', :collection => comment.children %>
</ul>
<% end %>
</li>
To save a nested comment, simply fill in the parent_id and awesome_nested_set will do the rest. I don't think rolling your own solution will be any more elegant than this.
Update: Looks like the awesome_nested_set hasn't been updated in some time. Check out ancestry instead. Does basically the same things.

A tree structure, yes, is a good idea - however it is the query execution itself that is of the utmost concern and i don't think most tree implementations as a gem take this into account and whilst nested set is alright - i reckon if something really bad happened, its overcomplicated in terms of write operations.
I'd check out Recursive CTEs - whilst not database agnostic, it gives you a nice data structure to work with without having to have extra attributes to track.

I used to do something like this by adding the following fields to the comments table:
attached_to_controller (string)
attached_to_id (int)
Then when showing I would make an AJAX call to the comments index and filter based on these two fields.
Of course, when creating comments you need to pass the appropriate values for these fields.

Related

Rails and factory patterns

Imagine a Rails project that looks up animal celeberties based on their names. This Rails app is backed by an external service that does the actual lookup. The service returns back results based on a key. For example, if I make a request to this external api like [GET] /animal?name=benji, I would get back something like {"type":"dog", "legs":"4", "tail-length":"short", "collar":"blue"}. However, if I pass in ...?name=flipper to the animal endpoint, I would get back {"type":"dolphin", "color":"gray", "food":"fish"}. (The data is returned in actual JSON or XML. I am just using pseudo code here to communicate the point.)
My first question is this... Given that the attributes of the return call vary based on data which is passed in, when unmarshaling a response (for lack of a better term) into a "model" object, does it make sense to implement some type of factory pattern (ala Design Patterns in Ruby, by Russ Olsen, Chapter 13) to create objects of an appropriate class? Are there other approaches that would make sense?
My next question is this, lets say that I want to display a list of all animals on a web page (using ERB templates.) Does it make sense to create different partial templates (eg _dolphin.html.erb and _dog.html.erb) and then put a case in the main list view that can deligate rendering each list item to an appropriate template.
For example:
list.html.erb...
<ul>
<% for animal in #animals.each %>
<li>
<% if animal.type == 'dog' %>
<%= render :partial => 'dog', :locals => {:animal => animal} %>
<% elsif item.type == 'dolphin' %>
<%= render :partial => 'dolphin', :locals => {:animal => animal} %>
<% else %>
<%= render :partial => 'generic_animal', :locals => {:animal => animal} %>
<% end %>
</li>
<% end %>
</ul>
(Here animal.type=='dog' is intentional. I am not using a symbol (:dog) because the data returned back from the API is a string value, and it is used to populate the animal.type attribute. Bad, I know.)
The project that I am working on is using this approach right now. (Obivously, I have changed the elements/domain.) I am wondering if this is a valid approach, and/or if others have dealt with similar problems and how they went about it.
Thanks!
I'd say create a single model and a single view which contains all possible attributes (can't be an infinite number ;) ).
And then you have an
if attribute_x exists then
display it
end
if attribute_y exists then
display it
end
for each attribute.
If you create a view for each animal this wouldn't be DRY at all, 'cause you'll repeat yourself sooo many times, just knowing that each animal has favorite food and a color, etc.. Another reason: If the API changes a bit, and an animal gathers or looses an attribute you would have to adapt this change.
With just one view, it would be all fine for all time.
If you want to be super-sure that you gather all attributes, you could place an array of all known attributes inside your controller and if there's something unknown: write it to a log file.
I'd only choose the way of 'one view per animal' if you want to be able to display things completely different for some animals. But then you could also tell your controller that it should choose another view if name = 'Donkey Kong'. you know what I mean.

What's the best way to edit many objects of a single class in one Rails form?

I'm working on a Rails form that will allow the user to edit the attributes of many objects of a class with a single submission. My initial instinct was to create an outer form_for block and then iterate through the items within it using fields_for.
However, there is no object that bears a one-many relation to the objects the form will modify, and so it seems to me that there is no object that would be correct to pass into form_for.
In any case, what I'd like to see is an example of a form that modifies multiple objects simultaneously without appealing to a "parent" object. Perhaps this will involve form_tag?
(Note: I'm working in haml, so answers in haml would be awesome though unnecessary.)
Well, having a parent object will make it easier.
For bulk updates of many objects, the key is to use the right input name so that rails will parse the params as a array, i.e.
#posts.each do |post|
fields_for "posts[#{post.id}]", post do |p|
p.text_field :name
p.hidden_field :id
end
end
Have a look at the generated html source to see what name attribute the text input gets. If this is done right, params[:posts] will now be a hash in the controller which you can then update.
http://railscasts.com/episodes/165-edit-multiple should be relevant too
There are some extra wrinkles to my actual situation, but here's some pseudocode that I hope will illustrate the approach I've taken:
= form_tag url_for(:controller => "the_controller",
:action => "update") do
#objects_to_be_updated.each do |object|
= check_box_tag "[desired_path][through_the][parameters_hash]", true, object.boolean_attibute
= text_field_tag "[another_path][through_the][parameters_hash]", object.text_attribute
end
end
And so on.
Using the _tag variants of the form helpers, which don't require association with an Active Record model, is a bit of a pain but also seems to give you more control over structure of the resulting parameters.

Rails - using group_by and has_many :through and trying to access join table attributes

For some background, please see my question: Rails app using STI -- easiest way to pull these records?
I have some models joined with a has_many through relationship using a third join table (Recipe and Ingredient joined through RecItem).
So the guys on the prior question helped me to group my recipe's ingredients by type - this is exactly what I wanted. However, now that I am accessing #recipe.ingredients instead of #recipe.rec_items, I can't get back to the data stored in the rec_items join table (stuff like amount of ingredient, how long to cook it, etc...)
Prior to using the group_by, I was iterating through each recipe's rec_items like so:
<% #recipe.rec_items.each do |rec_item| %>
<%= rec_item.ingredient.name %><br />
<%= rec_item.amount %><br />
<% end -%>
Now that I am grouping #recipe.ingredients, how can I follow back to the rec_items related to this recipe? (If I do ingredient.rec_items it gives me rec_items for all recipes...again, I can do this with clunky statements like:
ingredient.rec_items.find_by_recipe_id(#recipe.id).amount
but that seems wrong...) Is there an easy way to accomplish these goals? (getting a list of a particular recipe's ingredients, sorted/grouped by :type, while still being able to access the additional info in rec_items for each recipe/ingredient pairing?)
Thanks!
Posting same answer again, with a little modification:
You can use group_by here. By the way you better use includes in the query for recipe, otherwise there are going to be many queries.
recipe.rec_items.group_by {|ri| ri.ingredient.type}.each do |type, rec_items|
puts type
rec_items.each do |rec_item|
puts rec_item.inspect
puts rec_item.ingredient.inspect
end
end

How to make the view simpler, the controller more useful?

This question relates to cleaning up the view and giving the controller more of the work.
I have many cases in my project where I have nested variables being displayed in my view. For example:
# controller
#customers = Customer.find_all_by_active(true)
render :layout => 'forms'
# view
<% #customers.each do |c| %>
<%= c.name %>
<% #orders = c.orders %> # I often end up defining nested variables inside the view
<% #orders.each do |o| %>
...
<% end %>
<% end %>
I am fairly new to RoR but it seems that what I'm doing here is at odds with the 'intelligent controller, dumb view' mentality. Where I have many customers, each with many orders, how can I define these variables properly inside my controller and then access them inside the view?
If you could provide an example of how the controller would look and then how I would relate to that in the view it would be incredibly helpful. Thank you very much!
I don't think there is anything drastically wrong with what you're doing. Looping through the customers and outputting some of their attributes and for each customer, looping through their orders and outputting some attributes is very much a view-oriented operation.
In the MVC architecture, the controller has responsibility for interacting with the model, selecting the view and (certainly in the case of Rails) providing the view with the information it needs to render the model.
You might consider extracting the code into a view helper though, if you have that exact code repeated more than once. You could even genericize it, passing in the name of a model and association. I haven't tested it, but you should be able to do something like this:
def display_attributes(models, association, attribute, association_attribute)
content = ''
models.each do |m|
content << "<p>#{m.attribute}</p>"
associated_models = m.association
associated_models.each do |am|
content << "<p>#{am.association_attribute}</p>"
end
end
content
end
Then in the view, you could use the helper like this:
<%= display_attributes(#customers, orders, name, name) %>
Obviously you would change the HTML markup within the helper method to suit your requirements. Note that if you're not using Rails 3 then you'll want to escape the output of the attribute names in the helper method.
I don't think there's anything wrong with your code. I'd just suggest for you to use a :include in your find
#customers = Customer.find_all_by_active(true, :include => :orders)
to reduce the number of queries.
I see nothing wrong with the code as you showed.
You are mixed up about the "intelligent controller, dumb view" approach though, i tend to prefer the "skinny controller, fat model", so indeed the view should be dumb, but you put the intelligence inside your model, and your helpers (or use a presenter), but definitely not in the controller.

Rails: Refactoring, views, helpers: how does it all go together?

Warning: Noob here.
I know this is a trivial subject but I'm having a lot of difficulty in figuring out how exactly I can simplify my views by moving parts of them into helpers. For example, I've always read that conditionals in your views are prime candidates for extraction into helpers, but I couldn't really find examples of this, and my attempts to achieve this failed.
For example, suppose I have:
#index.html.erb
<% for beast in #beasts do -%>
<% if beast.dead? -%>
<%= beast.body %>
<%= link_to "bury", bury_beast_path( :id => beast.id ) %>
<% else -%>
<%= beast.body %>
<%= link_to "kill!", kill_beast_path( :id => beast.id ) %>
<% end -%>
<% end -%>
It annoys me a little to have this in my view, but how exactly could I move this to a helper instead? And further simplify it, if possible. (I've read somewhere that conditionals are bad but it's just beyond me how you could program anything without them.)
Another example: I need to id my body tags with the format controller_action. The best I've got so far is this:
#index.html.erb
<body id="<%= controller_action %>">
…and…
#application_helper.rb
def controller_action
#id = #controller.controller_name + "_" + #controller.action_name
end
I'm no expert, but that's still ugly even to me.
To make things more complicated, Ryan Singer said something I liked: to treat ERB like an image tag, using helpers to "reveal intention". Then in the next breath saying that you should have no HTML in helpers for that is the way to hell. WTF? How are both things compatible? If it's come to the point where you can just declare behaviors in the view, surely there should be a lot of HTML to be rendered behind the scenes? I can't grasp it.
So, that's basically it. I'd appreciate if anyone could share some thoughts on this, or point me to some good in depth reading on the subject – which I've found to have a really weak coverage on the web. I've already googled it to exhaustion but who knows.
Refactoring makes your views easier to maintain. The problem is choosing where the refactored code goes.
Your two choices are partials and helpers. There's no stone-set rules dictating which should be used where. There are a couple of guidelines floating around like the one stating that helpers should not contain HTML.
Generally partials are better suited for refactoring sections that are more HTML/ERB/HAML than ruby. Helpers on the other hand are used for chunks of ruby code with minimal HTML or generating simple HTML from parameters.
However, I don't agree with the sentiment that helpers should contain no HTML at all. A little is ok, just don't over do it. The way helpers are processed hinder their use for producing large amounts of HTML. Which is why it's suggested that your helpers contain minimal amounts of HTML. If you look at the source the helpers that ship with rails you will notice that most of them generate html. The few that don't, are mainly used to generate parameters and evaluate common conditions.
For example, any of the form helpers or link_to variants fit the first form of helpers. While things like url_for and logged_in? as supplied by various authentication models are of the second kind.
This is the decision chain I use to determine whether to factor code from a view into a partial or helper.
Repeating or nearly identical statements producing a single shallow html tag? => helper.
Common expression used as an argument for another helper? => helper.
Long expression (more than 4 terms) used as an argument for another helper? => helper.
4 or more lines of ruby (that is not evaluated into HTML)? => helper.
Pretty much everything else => partial.
I'm going to use the code you're looking to refactor as an example:
I would refactor the view in the question this way:
app/helpers/beast_helper.rb:
def beast_action(beast)
if beast.dead?
link_to "bury", bury_beast_path(beast)
else
link_to "kill!", kill_beast_path(beast)
end
end
app/views/beasts/_beast.html.erb:
<%= beast.body %>
<%= beast_action(beast) %>
app/views/beasts/index.html.erb:
<%= render :partial => "beast", :collection => #beasts %>
It's technically more complicated, because it's 3 files, and 10 lines total as opposed to 1 file and 10 lines. The views are now only 3 lines combined spread over 2 files. The end result is your code is much more DRY. Allowing you to reuse parts or all of it in other controllers/actions/views with minimal added complexity.
As for your body tag id. You should really be using content_for/yield. For that kind of thing.
app/views/layouts/application.html.erb
...
<body id="<%= yield(:body_id) %>">
...
app/views/beasts/index.html.erb
<% content_for :body_id, controller_action %>
...
This will allow you to override the id of the body in any view that requires it. Eg:
app/views/users/preferences.html.erb
<% content_for :body_id, "my_preferences" %>
The first thing I'd do would be this:
#index.html.erb
<%= render #beasts %>
#_beast.html.erb
<%= beast.body %>
<%= link_to_next_beast_action(beast) %>
#beast_helper.rb
def link_to_next_beast_action(beast)
if beast.dead?
link_to "bury", bury_beast_path( :id => beast.id )
else
link_to "kill!", kill_beast_path( :id => beast.id )
end
end
What I've done is separate out the rendering of the beast into a partial which uses collection semantics.
Then I've moved the logic for showing the kill/bury links into a beast helper. This way if you decide to add another action (for example, 'bring back from dead'), you'll only have to change your helper.
Does this help?
A third choice is to use a view model from the Cells gem. This is a very popular framework that brings object-orientation to the view layer in Rails.
# app/cells/beast/cell.rb
class Beast::Cell < Cell::Concept
def show
return dead if model.dead?
kill
end
private
def dead
link_to "bury", bury_beast_path( :id => model.id )
# you could render a view here, too!
end
def kill
link_to "kill!", kill_beast_path( :id => model.id )
end
end
You then render a view model using a helper (in the view or controller).
# app/views/beasts/index.erb
<%= concept(:beast, #beast).call %>
<%-# this returns the link content %>
That's all! You can test this cell isolated in a separate test. Cells also give you view rendering, view inheritance and many more things.
As an example, you could use a view for the kill link.
# app/cells/beast/cell.rb
class Beast::Cell < Cell::Concept
# ..
def kill
render :kill
end
end
This renders the cell's killer view.
# app/cells/beast/views/index.erb
<%= link_to "kill!", kill_beast_path( :id => model.id ) %>
Note the location of the view, it's nicely packaged into the cell directory.
And, yes, cells can do HAML and any other template engine supported by AbstractController.
Another startegy would be to not use templates and helpers at all.
For rendering you could :
render your views directly from your controllers using render(:inline => ). If you still want to keep Views and Controllers formally separated you can create modules / mixins that you include into the controllers.
or create your own view classes and use them to render your response.
The idea behind this is that helpers and rails erb templating system don't take advantage of OOP, so that at the end of the day you can't define general behaviours that you'll specialize according to each controller's/request's needs; more often than not one ends up rewriting very similar looking chunks of code, which is not very nice from a maintenance standpoint.
Then if you still need some helper methods (eg. form_tag, h, raw, ...) you only have to include them in your controller / dedicated view class.
See this : rails-misapprehensions-helpers-are-shit for a fun but useful article.
EDIT: to not sound like a complete douche, I'd say implementing this depends on how big your application is supposed to be, and how often you're going to have to update your code. Plus, if you're delegating the design to a non-programmer, he/she may well be in for some programming courses before digging into your code, which admittedly would be less directly understandable than with templates syntax.

Resources