Search code examples
ruby-on-railsseparation-of-concerns

Separation of concerns dilemma in Rails


I'm trying to make logging for my rails app and have some dilemmas over philosophy used in rails. My app has Link model which has_many Hits:

class Link < AR::Base
  has_many :hits
end

class Hit < AR::Base
  belongs_to :link
end

Now each time the link is being hit, I call hit! method to record a request on the link (to keep the controller skinny I make the model fat):

class LinksController < ApplicationController
  def hit
    link = Link.find(params[:id])
    link.hit!(request)
  end
end

class Link < AR::Base
  def hit!(request)
    params = extract_data_from_request(request)
    hits.create(params)
  end
end

Now here's where I'm confused. I want to record the data that came with request object (like remote ip, referrer, user agent, etc.) so I need to pass request object down to model, but I believe that this does not conform to "separation of concerns" and blurs responsibility lines in MVC design pattern (of course, correct me if I'm wrong). Also if I'll create a Hit object in controller itself, then I'm making skinny model and fat controller:

class LinksController < ApplicationController
  def hit
    hit_params = extract_data_from_request(request)
    Hit.create(hit_params.merge(:link_id => params[:id])
  end
end

Although the latter case makes testing much easier (I don't need to mock request in model specs) - it just doesn't seem right.

Any advice on this - much appreciated.

P.S. extract_data_from_request(req) method is placed in appropriate places where needed. It returns a hash of needed attributes for Hit object.


Solution

  • Personally I would be wary of over-thinking these things too much.

    The concept of a hit is very much tied to a website or web application, as is the concept of a (HTTP) request. The fat controller anti-pattern is more about having lengthy controller actions that contain ActiveRecord find statements and business logic (often characterised by if/elsif/else blocks) that can easily be extracted into the model.

    Controllers have certain orchestration responsibilities. Creating an object within one isn't a heinous crime. After all, we do it all the time in our create actions.