guidelines for where to put classes in Rails apps that don't fit anywhere
Good question - i don't have a concrete answer for you
but I recommend checking out this post
- http://blog.codeclimate.com/blog/2012/02/07/what-code-goes-in-the-lib-directory/
- be sure to read through all the comments
on a current project i have a ton of non-ActiveRecord objects under app/models, it works but not ideal
i put 're-useable' non application specific code under lib
other alternatives I have tried on side projects (say we have a bunch of command objects)
rails is a pain when it comes to namespaces under app, it loads everything up into the same namespace by default
app/
commands/
products/create_command.rb # Products::CreateCommand
products/update_price_command.rb # Products::UpdatePriceCommand
alternate, everything besides rails under src or an app_name directory
app/
src/
commands/
create_product.rb # Commands::CreateProduct
update_product_price.rb # Commands::UpdateProductPrice
I haven't come across a good solution for this, ideally the 2nd one is better, but would be nice to not have the additional directory under app, that way you open app and see controllers, commands, models etc...
Where's the best practice location for non-table-related classes in Rails apps?
It depends what is the purpose of this class. If this is something like a model. On example, by default mailers are put in app/models
and it isn't ActiveRecord. If you want to add some classes that are used in your application in many places and it isn't model-related, then probably the best place is to put it in a lib
directory. If you want to reuse it, you can also create a plugin with your class and then it would be placed in vendor/plugins
.
You can also put it in config/initializers
if it fits into this category.
To sum up: it depends what your class is about. Put it in a place where it fits. If you have doubts, put it in lib
directory.
Where do I place my non-model classes?
For service-type objects I put them in app/services.
For model-type objects I put them in app/models. I do not think it is necessary to inherit from ActiveRecord to be considered a model.
I think your object classifies as a 'service object' since it is designed to wrap the service of uploading a file, it isn't really a domain object like a model would be.
OO Design in Rails: Where to put stuff
Because Rails provides structure in terms of MVC, it's natural to end up using only the model, view, and controller containers that are provided for you. The typical idiom for beginners (and even some intermediate programmers) is to cram all logic in the app into the model (database class), controller, or view.
At some point, someone points out the "fat-model, skinny-controller" paradigm, and intermediate developers hastily excise everything from their controllers and throw it into the model, which starts to become a new trash can for application logic.
Skinny controllers are, in fact, a good idea, but the corollary--putting everything in the model, isn't really the best plan.
In Ruby, you have a couple of good options for making things more modular. A fairly popular answer is to just use modules (usually stashed in lib
) that hold groups of methods, and then include the modules into the appropriate classes. This helps in cases where you have categories of functionality that you wish to reuse in multiple classes, but where the functionality is still notionally attached to the classes.
Remember, when you include a module into a class, the methods become instance methods of the class, so you still end up with a class containing a ton of methods, they're just organized nicely into multiple files.
This solution can work well in some cases--in other cases, you're going to want to think about using classes in your code that are not models, views or controllers.
A good way to think about it is the "single responsibility principle," which says that a class should be responsible for a single (or small number) of things. Your models are responsible for persisting data from your application to the database. Your controllers are responsible for receiving a request and returning a viable response.
If you have concepts that don't fit neatly into those boxes (persistence, request/response management), you probably want to think about how you would model the idea in question. You can store non-model classes in app/classes, or anywhere else, and add that directory to your load path by doing:
config.load_paths << File.join(Rails.root, "app", "classes")
If you're using passenger or JRuby, you probably also want to add your path to the eager load paths:
config.eager_load_paths << File.join(Rails.root, "app", "classes")
The bottom-line is that once you get to a point in Rails where you find yourself asking this question, it's time to beef up your Ruby chops and start modeling classes that aren't just the MVC classes that Rails gives you by default.
Update: This answer applies to Rails 2.x and higher.
in rails, where is right place to put methods that need to be available 'anywhere'
This is what lib/
is for. I have a file at lib/deefour.rb
with
require "deefour/core_ext"
module Deefour; end
I put custom methods in lib/deefour/helpers.rb
module Deefour
module Helpers
extend self
def some_method
# ...
end
end
end
and core monkey patches in lib/deefour/core_ext.rb
class String
def my_custom_string_method(str)
# ...
end
end
In config/initializers/deefour.rb
I put
require "deefour"
In your config/application.rb
make sure you have
config.autoload_paths += Dir["#{config.root}/lib"]
Finally, in ApplicationController
(for controllers), ApplicationHelper
(for views), and wherever else I need it (ie. a specific model here and there) I simply do
include ::Deefour::Helpers
Rails: Where do you put non-model code?
You should be able to use lib
folder in your root directory (unless it's changed in Rails 3).
You can refer classes from there without require
statement.
Where to put constraint classes in Rails project
lib/
would be the appropriate place. If you want to make it cleaner, put it in lib/constraint/authenticated.rb
and define your constraints like so
module Constraint
class Authenticated
def matches?(request)
# stuff
end
end
end
and in your routes.rb
constraints Constraint::Authenticated.new do
match 'account' => 'account#index'
end
How to organize actions that don't fit into the normal MVC
To think RESTfully, you should probably stop thinking of them as "controllers with actions" and start thinking of them as "objects that can be created/updated etc" - controllers are just proxies for the views that show the results of creating/updating an object.
A lot of the time, I've found that an extra action is really just a variation of "update" - just with its own special requirements (eg only certain people can update it or whatever). That sort of logic can often go inside the model itself (eg "MyModel#can_be_edited_by?(some_user)").
Sometimes you find that actually you have an extra "hidden" model that needs its own RESTful interface.
Eg with your "take" a survey - I'm guessing, but what you have is something like a "SurveyResult" and a person can "create "survey" but when they "take" a survey, they are actually creating a "SurveyResult" (the other commentor called this a "SurveyParticipation" - but it's the same thing).
The thing being that you will probably have multiple SurveyResults that each belong_to :survey and belong_to :some_user_model.
Then you can set up nice restful routes such as:
/surveys/123-my_favourite_colour/results
which will return a set of all the results for a single survey
This is actually the RESTful way to view this part of your object-space.
As to sharing a survey - that's a more intersting question. It depends on how you've got your authorisation setup for "sharing". It also depends on what you mean by "share".
Are you sharing the results of a survey, or sharing the survey-object itself (so another user can edit the questions) or are you (as a person that has just taken part in a survey) sharing a link to the survey so that your friends can also take the survey?
For the first two above - I'd consider a "SurveyPermission" class that belongs_to :survey and belongs_to :some_user_model.
The you can create a SurveyPermission for another user - and Surveys can be edited by the creator- or anybody that has a permission to edit it.
Thus the share action is to create a SurveyPermission.
Though to be honest - your SurveyPermission is likely only to be used for create and delete, so it may be simpler to stick those two actions in the Survey controller.
For the latter - well, that's just sending a "create_survey_result(@survey)" link to somebody...
Update:
I don't generally bother with namespaces unless there are two resources named the same thing (but in different contexts). You only need namespaces to disambiguate between them and that doesn't seem to be the case here.
In this case - the only namespacing that occurs is in the routing:
map.resources :surveys do |s|
s.resources :results
s.resources :shares # ???
end
gives such routes as:
new_survey_path
surveys_path
new_survey_result_path(@survey)
survey_results_path(@survey)
Related Topics
Message Queues in Ruby on Rails
How to Programmatically Generate Heroku-Like Subdomain Names
How to Build a Ruby Hash Out of Two Equally-Sized Arrays
The Command Rbenv Install Is Missing
Method_Missing Gotchas in Ruby
Rubocop Line Length: How to Ignore Lines with Comments
Tutorials or Screencasts on Building a Rest Web Service on Rails
Convert String to Symbol-Able in Ruby
Advantage of Tap Method in Ruby
Will Uuid as Primary Key in Postgresql Give Bad Index Performance
How to Globally Configure Rspec to Keep the '--Color' and '--Format Specdoc' Options Turned On
Openssl VS Gpg for Encrypting Off-Site Backups
How to Specify Local .Gem Files in My Gemfile
God VS. Monit for Process Monitoring
How to Log Every Method That's Called in a Ruby Program
Vagrant/Virtualbox Vm Provisioning: Rbenv Installs Successfully But Subsequent Uses in Script Fail