I'm trying to make a comment system, identical to the stackoverslow's one. There's a 1-st level comment, and a bunch of a second level comments, attached to it. The first straightforward idea that came up in order to do that, was to create a separate model, sub_comment. But I still got this feeling, that I should inherit the initial comment's properties, only adding a simple comment-id joint.
I'm still a Rails newbie, so the question is - can I do that? How? Is it a simple
class sub_comment < comment
model inheritance? But of course, I'm gonna need all the controller methods in order to add/delete these sub-comments. Or should I make a single comment model, where the 1-st level comment will have a nil as a parent?
What would be the right solution?
PS. Fell free to advice any book that covers the subject. I'm on a self education here, so I'd love to read everything that could clarify any future app architecture question.
I find it easier to have a simple tree structure with two pointers to make back-tracking easier:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
acts_as_tree
belongs_to :post
named_scope :top_level, :conditions => { :parent_id => nil }
end
This makes it easy to retrieve all comments for a particular Post:
#comments = #post.comments.top_level
You can even pull up multiple levels of comments using AJAX or by fetching one layer after the next:
# Repeat while comments are found
loop do
level = #post.comments.find(:all, :conditions => { :parent_id => #comments.collect(&:id) })
if (level.empty?)
break
else
#comments += level
end
end
These can be sorted as required for presentation purposes.
One option is to have two separate classes, and put the shared behavior in a module. That way you're not doing Single Table Inheritance (which is what your inheritance example would mean, and it's something I have had problems with) but you also aren't duplicating code.
Conceptually, you're trying to create a self referencing association. This is certainly the cleanest way to go as you don't have extra tables and models to worry about, though it can be complex to setup.
I'd recommend checking out the Self Referential Association Railscast, it will walk you step by step how to get started.
The short and sweet answer is that I would go with your "parent id is nil" methodology for top-level comments and clearly sub comments would have parent id's. Remember, DRY rules the day in the Rails world. If you can use one class (in this case it makes sense), you should.
Related
I read many blogs, and one of the themes that comes across often is that concerns (at least the way Rails defines them) are damaging to software. On balance I agree - simply including behaviour into models is violating the single responsibility principle. You end up with a god-class that does too much.
But as with many of the opinions gleaned from blogs, an alternative architecture is rarely provided.
So let's take an example app, loosely based on one I have to maintain. It's inherently a CMS, as many Rails apps tend to be.
Currently each model has a large number of concerns. Let's use a few here:
class Article < ActiveRecord::Base
include Concerns::Commentable
include Concerns::Flaggable
include Concerns::Publishable
include Concerns::Sluggable
...
end
You can imagine that 'Commentable' would require only a small amount of code added to the Article. Enough to establish relationships with comment objects and provide some utility methods to access them.
Flaggable, allowing users to flag inappropriate content, ends up adding some fields to the model: flagged, flagged_by, flagged_at for example. And some code to add functionality.
Sluggable adds a slug field for referencing in URLs. And some more code.
Publishable adds publish date and status fields, with yet more code.
Now what happens if we add a new kind of content?
class Album < ActiveRecord::Base
include Concerns::Flaggable
include Concerns::Sluggable
include Concerns::Publishable
...
end
Albums are a bit different. You can't comment on them, but you can still publish them and flag them.
Then I add some more content types: Events and Profiles, let's say.
I see a few problems with this architecture as it stands:
We have multiple database tables with exactly the same fields
(flagged_by, published_on etc.)
We can't retrieve multiple content types at once with a single SQL query.
Each model supports the duplicated field names with the included concerns, giving each class multiple responsibilities.
So what's a better way?
I've seen decorators promoted as a way to add functionality at the point where it's needed. This could help solve the issue of included code, but the database structure isn't necessarily improved. It also looks needlessly fiddly and involves adding extra loops to the code to decorate arrays of models.
So far my thinking goes like this:
Create a common 'content' model (and table):
class Content < ActiveRecord::Base
end
The associated table is probably quite small. It should probably have some kind of 'type' field, and maybe some things common to absolutely all content - like a type slug for URLs perhaps.
Then rather than adding concerns we can create an associated model for each behaviour:
class Slug < ActiveRecord::Base
belongs_to :content
...
end
class Flag < ActiveRecord::Base
belongs_to :content
...
end
class Publishing < ActiveRecord::Base
belongs_to :content
...
end
class Album < ActiveRecord::Base
belongs_to :content
...
end
...
Each of these is associated with one piece of content, so the foreign key can exist on the feature's model. All the behaviour relating to the feature can also exist solely on the feature's model, making OO purists happier.
In order to achieve the kind of behaviour that usually requires model hooks (before_create for example) I can see an observer pattern being more useful. (A slug is created once a 'content_created' event is sent, etc.)
This looks like it would clean things up no end. I can now search all content with a single query, I don't have duplicated field names in the database and I don't need to include code into the content model.
Before I merrily unleash it on my next project, has anyone tried this approach? Would it work? Or would splitting things up this much end up creating a hell of SQL queries, joins and tangled code? Can you suggest a better alternative?
Concerns are basically just a thin wrapper around the mixin pattern. It is a very useful pattern for composing pieces of software around reusable traits.
Single Table Inheritance
The issue of having the same columns across several models is often solved with Single Table Inheritance. STI however is only really suited when the models are very similar.
So lets consider your CMS example. We have several different types of content:
Page, NewsArticle, BlogPost, Gallery
Which have pretty much identical database fields:
id
title
content
timestamps
published_at
published_by
# ...
So we decide to get rid of duplication and use a common table. It would be tempting to call it contents but that is extremely ambiguous - content of the content ...
So lets copy Drupal and call our common type Node.
class Node < ActiveRecord::Base
include Concerns::Publishable
end
But we want to have different logic for each type of content. So we make subclasses for each type:
class Node < ActiveRecord::Base
self.inheritance_column = :type
include Concerns::Publishable
end
class NewsArticle < Node
has_and_belongs_to_many :agencies
end
class Gallery < Node
has_and_belongs_to_many :photos
end
# ...
This works well until the STI models start to diverge for too much from each other. Then some duplication in the database schema can be a far smaller problem than the massive complications caused by trying to shoehorn everything into the same table. Most CMS systems built on relational databases struggle with this issue. One solution is to use a schemaless non-relational database.
Composing concerns
There is nothing in that says that concerns require you to store on the models table. Lets look at several of the concerns you have listed:
Flaggable
Sluggable
Commentable
Each of these would use a table flags, slugs, comments. The key is making the relation to object that they flag, slug or comment polymorphic.
comment:
commented_type: string
commented_id: int
slugs:
slugged_type: string
slugged_id: int
flags:
flagged_type: string
flagged_id: int
# ...
class Comment
belongs_to: :commented, polymorphic: true
end
module Concerns::Commentable
# ...
has_many: :comments
end
I would suggest that you look at some of the libraries that solve these kind of common tasks such as FriendlyId, ActsAsTaggedOn etc to see how they are structured.
Conclusion
There is nothing fundamentally wrong with the idea of parallel inheritance. And the idea that you should forgo it just to placate some kind extreme OO purity ideal is ridiculous.
Traits are a part of object orientation just any other composition technique. Concerns are however not the magic-fix all that many blog posts would have you believe.
I am working at a web app in Rails that behaves much like a CMS: there are articles, which have a text attribute that contains links to other articles (or other object classes). I'm currently storing the attribute as HTML.
Is there any good way to model those links in a way that is relative easy to change, and contain the reference to the object id, instead of the absolute url?
One of the solutons I was thinking was to use some kind of special mark-up, such as:
[link_to "Text for the link", Article:12]
where 12 is the id of the article it links to. This mark-up will be parsed when the text is rendered.
The downside of this is that I have to hack into TinyMCE (the editor I'm thinking of using to edit the HTML) so that it can insert links to other objects, by accessing the database and automatically assigning the object type and ID (the person who's editing the texts doesn't know the id's).
Is there any simple solution to this?
Or should I stick to using absolute urls (which, besides maintenance issues, is annoying in development, as they will always point to production and that is confusing for me)?
Additionally, does anyone have similar examples in other languages (php, Wordpress, other CMS, etc) that tackle this problem in a nice way? I'm thinking, this is pretty vital in CMS, and can reduce a lot of man hours if a nice system can handle all those links.
EDIT:
Another possible solution that I'm thinking about is letting the person copy the link of the article directly in the code, but it should, upon submission, generate the correct association id and make it so that if the url structure changes, the link is always up-to-date. I'd like to hear your opinions and experience with this approach, if you have tried it.
The challenge with this approach is parsing the link with Rails and finding out that it points to an Article, and that article has the id ##. Then I have to insert a shortcode that will always translate, upon parsing and rendering, to an actual link to that article.
I found a method that could make this feasible:
Rails.application.routes.recognize_path
But there may be some caveats that I don't see right now...
EDIT no. 2
I also want to specify that I chose CKEditor as the content editor, but I will consider other ones if there are clearer advantages.
I have built something similar using a shortcode system which would allow me to call specific methods on the model and replace the shortcode in the text:
Helper
def parse_shortcode(model)
text = model.text
text.gsub(/(\[#!\s?\w+\])/i) do |match|
result = model.try(match)
result.nil? '' : link_to(result[:text], result[:url])
end
end
Model
def contact_link
{ :text => self.name, :url => self.url }
end
View
<%= parse_shortcode(#article) %>
I haven't tested the above code and it is obviously a bit simplified but it explains my thought process behind this.
EDIT: Just to clarify my above example uses an invented shortcode syntax of [#! method]
An article can have many related_articles and at the same time this article can be related by many other articles, so it is best to model this as a many-to-many relationship.
One way to define this type of relationship in Rails is has_many :through.
To use has_many :through you have to create a join model, perhaps call it ArticleRelation. This model will have two fields, an article_id that represents the current article and a related_article_id that represents the article who’s being referred as related.
class Article < AR::Base
has_many :article_relations
has_many :related_articles, :through => :article_relations
end
class ArticleRelation < AR::Base
belongs_to :article
belongs_to :article_relation, :class_name => 'Article'
end
When creating self-referential relationships it’s important to remember that you’re only creating one side of the relationship. Although article_1 might list article_2 as related, there is no way to for article_2 to list article_1 as related. You'd need two ArticleRelation records to create a mutual relationship.
It’s difficult to think up appropriate names to define another side of the relationship so you can prefix both with the word “inverse” to give inverse_article_relations and inverse_related_articles. You also need to specify some additional options to make the relationships work. For inverse_article_relations you’ll have to specify the name of the other model as it can’t be inferred from the relationship name and you’ll also have to define the foreign key as related_article_id. For the inverse_related_articles relationship you need to specify the source as articles, as again it cannot be inferred from the name of the relationship.
has_many :inverse_article_relations, :class_name => "ArticleRelation", :foreign_key => "related_article_id"
has_many :inverse_related_articles, :through => :inverse_article_relations, :source => :article
Test it out, this should work for you per current requirement.
A solution I've seen in a lot of other CMSs is a combination of custom file browser in TinyMCE and page rewriting (similar to freakyDaz's answer).
TinyMCE has documentation and example code for implementing a custom browser. You'll have to provide the backend pieces, of course.
CKEditor has documentation for a similar feature as well.
Have your backend implementation return something that's easy to parse for the URLs (urlfor:Article:12, for instance), then have your rendering code replace those with actual URLs.
I just thought of another possible solution for the use case:
The admin user specifies the relationships before they edit the text (using Chosen.js, this can be done in a user-friendly way).
Then the person either submits the form to save the model or it can be done asynchronously.
When the relationships are saved, a shortcode is generated and displayed for each of them, and that short-code can easily be pasted in the text.
When displaying the text in the front-end, the text will be parsed for the shortcode, in a similar way to what #freakyDaz is suggesting.
In this way, I don't have to hack or create custom actions in the editor. I think it's a pretty pragmatical approach, but I'd like to hear your opinions. Of course, the admin who's making the text should be educated to follow the process in that order, but in my case very few persons can be admins (1 or 2), so it's manageable.
I am struggling to figure out how to get a relationship between several models. I have sales_leads which I need to view by company and by event. So, if someone looks up the company leads they can see everything across all events, but also see all leads by event. Not sure if this is ownership versus a where?
Should it be something like
Company.sales_leads where("event.event_id = ?", "2356")
Or Models:
sales_lead
belongs_to event
belongs_to company
Also, could I suggest you go right back to basics. First up, draw on a bit of paper precisely how you want the models to interact...
Do you want your model 'Foo' to have one or many 'Bars'? Or do you want Foo to have many Bars and Bars to have many Foos. If that makes sense!!
Don't write any code until you know what you exactly need.
Maybe start by watching a few of the following Railscasts:
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/17-habtm-checkboxes
http://railscasts.com/episodes/154-polymorphic-association-revised
http://railscasts.com/episodes/189-embedded-association
I have a project going on right now that is really big data model wise. I am trying to figure out the best way to handle inter-model relationships.
For the sake of brevity:
Car
has_many :passengers
has_many :items
or
Team
has_one :head_coach
has_many :coaches
has_many :players
belongs_to :owner
So from the show page I would see who is in the car and what items are in the car. I have some co-workers who think we should have a controller action called manage where they would click a link on the show page to manage the relationship between the other models
So the Team controller would have this
class TeamController < ApplicationController
# ... magic ...
def manage_players
#signed_players = Player.signed_players
#free_agents = Player.free_agents
end
end
The manage_players view would just have links to the actual RESTful actions on the appropriate controller to remove relationships etc...
Anyone have thoughts on how this should be accomplished?
That's an overly complicated approach, and the good news is, it's way simpler than you think.
Save yourself some trouble. The quick answer to your question is to use nested resources: you can have a single form that handles the Car and all the associated passengers/items, or the Team and its coach, players, etc.
The action/view you're describing would just be the edit action on the Car/Team. The manage action name is a nice idea and all, but the action you're really taking is an edit (nothing special, by what you're describing), so why confuse what's going on when the default is to call it edit?
If you want a live example of something that takes advantage of nested routes, check out rpglogger.com (it's my site). When you play around with it, notice the routes/URLs in the address bar.
It's also open source. Specifically relevant to your question is:
see the routes.rb file, and note how I define resources on sections twice - this actually gives me two different versions of the routes - one that's scoped to the LogBook, and one that's scoped to the objects in a section
see the world_object_form.haml (haml also rocks, FYI), which is both my new and edit form - yet it's short, rather uncomplicated, and pretty easy to read/undestand given what it does.
I'm looking for guidelines on when to know when a RESTful approach to a model and it's associations is proper, and when it's not. Perhaps it's the nature of my current application, but I'm finding that simple models with no associations work well with REST, but complex models with many has_many assocations really seem to complicate the view and the setup required in the controller. form_for calls start to become especially complicated.
Or perhaps it's my neophyte understanding. I've been doing Rails for over three years now, but REST and form helpers together seem to mystify me.
Make a resource of each top-level model in your system. By top-level, I mean models that are independent and have meaning outside of the associated model. Generally, that's most models. In the following example Position and Candidate are top-level. You could consider Candidate to be composed of PastEmployment and positions to which she has applied. Applications to positions and prior work history can be accessed through the Candidate resource, since they don't exist on their own.
Models
class Position
has_many :candidate_positions
has_many :candidates, :through => :candidate_positions
end
class Candidate
has_many :candidate_positions
has_many :positions, :through => :candidate_positions
has_many :past_employments
accepts_nested_attributes_for :past_employments
accepts_nested_attributes_for :candidate_positions
end
class PastEmployment
belongs_to :candidate
end
class CandidatePosition
belongs_to :candidate
belongs_to :position
end
Routes
map.resources :positions
map.resources :candidates
Use a non-resourceful controller for interactions with the user that span models. For example, if you wanted to have a HomeController that shows available positions as well as recent candidates, that would be a new, plain controller. If you want to edit any of the information on this controller, cool! You already have controllers available to handle the form posts, that will automatically be wired with <% form_for #candidate %>. You can render your collection of positions with <%= render #positions %>, and because you've made them a resource, Rails will know to look in views/positions/_position.html.erb for the appropriate partial.
The end result should be that you're never writing logic to handle the persistence of an object in more than one place. That's when controllers get complex and forms get out of hand. It also means that Rails and external systems know where to retrieve and store objects. Same URL, same controller, just a different format.
You might check out this series of Railscasts, which talks about has-many relationships and forms in the context of REST:
Complex Forms Part 1
Complex Forms Part 2
Complex Forms Part 3
I don't know RoR, so I will make generate statements on REST.
REST and ROI treats the URLs as series of nouns, and it uses HTTP methods like GET, POST, PUT, and DELETE as the verbs. This model works for CRUD, and even for models with multiple associations. Since URLs represent resources, associated objects can be expressed as list of URLs.
However if your system requires more fine-grain verbs like validate, move, copy, honk, read, write etc. it may not suit REST.
disclaimer: I know rails, but I still am pretty much a newbie.
Short Answer: REST and form helpers are completely different areas.
Long answer:
As I understand it,Representational State Transfer is only loosely related to the actual rendering of forms and views.
REST really has to do with controllers, and to a certain extend models. The idea is that instead of trying to think about an entire conversation with a client, you write a webapp to respond in specific, predictable ways to individual client messages.
i.e., if a client GETs a model, you just retrieve it, format it for them, send it to them, and forget about it.
if a client POSTS an update of some sort, you change the webapps state to reflect that, send back any response, and then forget about it. Any future GET or POST will look at the new state, but not the message that created it.
So, really, whether or not an application is RESTful depends not really on how complicated the model is, but on how users interact with it. An app meant to be at least somewhat client-agnostic, that is data-centric, is a good candidate for REST. Something that relies heavily on sessions, and interacting with a specific user, and is process-centric, might not be such a good candidate.
On the other hand, you have Rails form helpers. These are great for scaffolding, but sometimes can be frustrating when you try to use them in more complicated ways.
So, what is your main question? Do you have a specific question about rails form helpers? about rails controllers? or something specific to REST?