I always seem to get stuck with 40 different models in the same folder namespaced like post.rb, post_comment.rb, post_rating.rb etc etc. Is there any way to make something like modules in rails, namespacing away everything with posts to a separate directory? (This directory could include the different controllers and views too).
Rails Engines seems promising but maybe there's something else that I've missed?
I think it would make the project easier to overview and to enter as a new collaborator.
If you have opinions against, please tell!
I use namespaces.
When you run command rails g scaffold blog/post, it'll generate Post under Blog namespace.
app/models/blog.rb
module Blog
def self.table_name_prefix
'blog_'
end
end
app/models/blog/post.rb
class Blog::Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
app/models/blog/category.rb
class Blog::Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
It seems your modelling has some problem.
A comment is a comment. A post can have comment, a photo can have comment. But it's definitely not good practice to have models like post_comment, photo_comment. Well, I only see "post_comment" in question, but I guess you name it for such reason.
You'll be busy to follow and work for these models with similar functionalities, and then their controller, their views. Same is true to "post_rating" etc.
I would not say 40+ models is too much. But seeing new Basecamp has 50+ models and Dispora has 30+ models without sub folders, it may worth a review on your app's architecture to cut some.
If you plan to use "comment", "rating" on others such as "photo", you can use the name directly, and plan associations, polymorphic, modules to DRY the code.
As an example you can place all of your post_*.rb models into a posts folder. Make sure to rename each model within the folder with Posts::Post* ie class Posts::PostRating < ActiveRecord::Base
Rather than update all the models' Class references in your codebase, in my opinion it is likely just easier to leave them all in the models directory and deal with the agony of having a bloated models directory.
Relevant Readings:
ActiveRecord: Can haz namespaces?
Namespaced models
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 through an old legacy project that has recently been updated. The models to the project are in an engine running separate from the controllers and views etc. The engine is running on rails 4.1.6 while the controllers etc are on Rails 3.
This has led to many issues with mass-assignment. I have created a little module that reads the db columns and white lists the attributes for that model. However in a case such as this NewsItem model which has associations and needs to accept attributes for those associations, the module doesn't work.
class Newsitem < ActiveRecord::Base
include MyAttrAccessibleModule
has_and_belongs_to_many :assets
has_and_belongs_to_many :boroughs
has_and_belongs_to_many :venues
I need to add
attr_accessible :asset1_attachment_remove,
:asset1_attachment_title,
:asset2_attachment_title,
:asset3_attachment_title,
:asset4_attachment_title,
:borough_ids,
:venue_ids
But finding all models that require this is a bit of a pain, seeing as there are well over 100.
Is there a way to highlight, find, test, discover in what other models this error might occur in also?
I think what you're looking for could be this:
Object.attributes.keys - Object.accessible_attributes
This should subtract all the whitelisted attributes from all of the available ones.
Thanks to #Stefan Dorunga for his suggestion. It led me in the right direction.
if reflect_on_all_associations(:has_and_belongs_to_many).any?
association = reflect_on_all_associations(:has_and_belongs_to_many)
association.each {|model| attr_accessible model.plural_name.singularize + "_id"}
association.each {|model| attr_accessible model.plural_name}
end
I query the model to see if it has a relationship where the relation id isnt explicitly listed and generate it dynamically.
The only downside is that I have to include the module after all the listed associations. I usually include modules at the top of a class. Whether this is standard practice or not I do not know.
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.
This question relates to using Ruby on Rails 3 with MongoMapper and EmbeddedDocument. Also, the ManyAssociation.
http://mongomapper.com/documentation/embedded-document.html
The MongoMapper examples (in the link above) show two separate classes:
class Order
include MongoMapper::Document
many :line_items
timestamps!
end
class LineItem
include MongoMapper::EmbeddedDocument
key :name, String
key :quantity, Integer
end
But this pollutes the global namespace with LineItem. Out of context, LineItem for what? And what if I want another model, say WishList, to also have a LineItem set?
So it is possible to embed the LineItem class inside Order, like this:
class Order
include MongoMapper::Document
many :line_items, :class_name => "Order::LineItem"
timestamps!
class LineItem
include MongoMapper::EmbeddedDocument
key :name, String
key :quantity, Integer
end
end
While this may be technically fine (yes?), will I run into design issues later on? Does it just make the code too ugly? Too complex?
Presumably, the existence of this in the Ruby language means someone thinks it's an ok idea?
One thing I always liked about Django is how it uses "apps" to group related model classes (and separate the namespaces). So my code above is also achieving that in Rails.
I don't see any technical problems to either approach. The problem I've run into is that when a class is embedded in it's parent and is also in the same file, I'll forget it's there. So if your namespaces it as Order::LineItem you can make an "order" folder in your "models" folder and put "line_item.rb" in there.
The other trouble is if you want to have a controller for Order::LineItem, you also have to namespace it and put it in a folder, and in the router it will look like:
resource :orders do
resources :line_items, :controller => "order/line_items"
end
Unless you know your app is going to have multiple types of line_items, I'd recommend not namespacing it—you could be over-coding if you did. For example, if you were later to need two types of line_items you might even find that some of your code could be re-used between models—and if you namespaced your first line_item you may find yourself de-namespacing it.
In Python, namespaces are considered a great thing that there should be more of. But when you think about the global namespace of your Rails app, it's not that cluttered. Gem authors are pretty good about keeping all of their classes namespaced in their one gem module, so in your Rails app you're going to have one namespace for each gem you're using (Rails itself is 5 gems, not sure how many global constants though), plus and a bunch of files included by Rails (e.g. SecureRandom). It turns out to be really nice to have those files "just there" and I've found in practice that namespace collisions are rare and you can easily work around them. I've only run into a namespace issue maybe once, but several times I've accidentally defined a "send" method on a model—a much more common problem with similar repercussions.
I'm currently using the awesome attachment-fu plugin for a Rails app, but as a novice developer, I've never encountered a scenario like the one I've found myself in.
Essentially, I'm using the attachment-fu plugin on two levels.
Is for user avatars in the user class.
Is to allow file attachments (PDFs, etc) in a messaging system.
My question is what the best use practice would be in these situations to remain DRY, clear, and consistent.
Clearly it would make no sense to define and execute the plugin in both classes, but there's something deeply strange to me (possibly unfounded) about just going ahead and setting it all up in the godly Application class.
Is there something in between, or is the parent class the way to go?
Thanks!
What's the DRY issue with defining the attachment_fu settings twice?
Unless the files are of the same type and being stored in the same place, you're not going to be repeating anything in the configuration.
Sure, you'll have two has_attachment declarations, but the options will mostly differ (one declaration for your avatars and the other for your pdf's etc.
99.99% of the code to handle attachment will be buried in the attachment_fu libs, your configuration code should be pretty DRY by default =)
Is "outsourcing" avatar support entirely to Gravatar an option? There are some Rails plugins that will display avatars hosted by Gravatar. You might not need to re-invent the wheel there.
What wfarr is describing would be single table inheritance, which is what I currently do in this situation. I have one table for Assets which contains all the necessary attachment_fu columns, plus an extra column called type, which will hold the actual model name. I have a model for assets and additional models for specific upload types that inherit from assets:
asset.rb:
class Asset < ActiveRecord::Base
... attachment_fu logic ...
end
avatar.rb:
class Avatar < Asset
... avatar specific attachment_fu logic ...
end
pdf.rb:
class PDF < Asset
... PDF specific attachment_fu logic ...
end
I would lean towards using a parent class, with subclassing for the different ways you intend to actually use the attachments in your application. It may not be the DRYest solution available, however, it lends itself to a logical pattern rather well.
Couldn't you use Polymorphic Associations?
I'm about to hit this in my app with attachment_fu, so I'm not exactly sure on attachment_fu, but for the old school File Column plugin, I would use Polymorphic Associations.
My "file" model would be:
class FileUpload < ActiveRecord::Base
belongs_to :fileable, :polymorphic => true
file_column :name
end
and then any models that needed a file attachment would be like:
class Company < ActiveRecord::Base
has_many :file_uploads, :as => :fileable
end
File Column is no good anymore as it borks on Safari 3.x and is no longer maintained. It was nice and simple though... Ah, the good old days...
For what it's worth, I think Patrick Berkeley has done a good job with handling multiple attachments through the Paperclip plugin. He's outlined his work here:
http://gist.github.com/33011