Oo Design in Rails: Where to Put Stuff

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.

Where to put a class that is used by helpers and controllers but is not a model?

A model doesn't necessarily need to be part of a full resource, with route and controller : if it maps some kind of data entity, it's a model - especially if it's a subclass of active_record.

So app/models/ is the perfect place for your Semester model.

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 is the right place to put predefined class (e.g. String, Symbol) extensions in Ruby on Rails?

I think that sticking your native class extensions in a new file in your lib folder and require-ing them in your environment.rb file should do it.

Where to place own module in rails app?

These generally are placed in a new directory off app called concerns - it's worth googling for more info about how that works.

here's a good ref of places to go look: How to use concerns in Rails 4

Where to put method that returns a collection

You can keep it in the Model, but instead of defining it as an instance method, define it as a class method:

def self.all_open
where("shops.closed != 1")
end

This way you can use it like this:

Shop.all_open

And even nest methods, like this:

Shop.all_open.where(field: "value")

UPDATE:

Like others pointed out, you could also use scopes (this is the preferred way):

class Shop < ActiveRecord::Base
scope :all_open, -> { where("shops.closed != 1") }
end

The usage would be the same:

Shop.all_open

Where Should I Put Rails Code that is Related to a Model but not Related to Database Operations?

You are right, the scan method should be inside the scanner class, for several reasons:

  • Naturalism: Scanning is what a scanner does; in an object oriented world, you have scanners and one of the things they do is scan stuff
  • Dry code: You will be able to call the scan method from anywhere; in controllers you would need to replicate it in every controller where you need it; not really dry
  • Fat models: It follows the "fat model, skinny controller" paradigm - have a look at this presentation
  • Language: It is idiomatic and intuitive. Some people may disagree with me, but I think Rails encourages you to think in English more than to think in Ruby. Just talking about it makes it clear that a scanner scans things

So yes, I would put the scan method inside the canner model.

How can I make rails helpers more object-oriented

If you create a new model you can make it inherit the methods from an existing class.

Example (app/models/mykindofstring.rb):

class Mykindofstring < String

def all_caps_and_reverse
self.upcase.reverse
end

end

Then from the controller:

@my_string = Mykindofstring.new('This is my string')

and finally calling it from the view:

<%= @my_string %>

displays as This is my string, while:

<%= @my_string.all_caps_and_reverse %>

displays as GNIRTS YM SI SIHT.

The class inherits from the string class, so all the methods that apply to a string also apply to a mykindofstring object. The method in it is just a new name for a combination of two existing string methods, but you can make it do whatever you want, such as a method to convert a time into a formatted string.

Basically it's just extending a current Ruby class with your own methods instead of using functions/helpers.

Major Edit

Based on Ians comment to this answer and his own answer to the above question, I played around a bit in Rails and came up with adding this to the application_helper.rb:

module ApplicationHelper
String.class_eval do
def all_caps_and_reverse
self.upcase.reverse
end
end
end

Now just call the method on any string in the app:

@string = 'This is a string.'
@string.all_caps_and_reverse

.gnirts a si sihT



Related Topics



Leave a reply



Submit