开发者

Best practice for handling two similar objects

Let's say I have two different controller/view sets that are basically identical.

Business hours and Staff hours

The first contains a business_id, the latter a staff_id. Otherwise they look the same. id, mon_start_time, mon_stop_time, tues_start_time, tues_stop_time, etc...

1) Is there a way I would use the same controller for these since they are so similar? It doesn't seem to make THAT much sense, but I just keep thinking about how similar they are and how much duplicate code there is.

开发者_开发百科

2) Additionally, for each one I have a form in a partial. I'm trying to use the same one for both business hours and staff hours. The partial, in the most simplified state looks like:

-form_for(obj) do |f|

=f.error_messages %p =select obj.class, :mon_start_time, hours to =select obj.class, :mon_stop_time, hours

=link_to 'Back', obj_path

So there are 3 unique things I need to pass in. The 'obj', either business_hours or staff_hours. That's okay in the form_for, but how do I get the lowercase controller name for the first parameter of the selects? I need 'business_hour', and 'staff_hour', from 'obj'. Then I need it to know the correct 'Back' link.

I know I can pass parameters into the partial, but I'm just curious if there's a slicker way of going about this. Thanks!


Duplicate code has a carrying cost, a cost to maintain it. We sometimes don't know how high that cost is until we refactor the duplicate code and find outselves breathing a sigh of relief: Now I can change the business rule in just one place. Now I can stop typing things twice.

You can use two controllers but still refactor the duplicate code. One way is to put the common code in a module included by both controllers.

module CommonStuff

  def stuff_that_is_the_same
  end

end

controller FooController < ApplicationController

  include CommonStuff

  def stuff_that_is_different
    # Stuff specific to Foo
    stuff_that_is_the_same
    # More stuff specific to Foo
  end

end

controller BarController < ApplicationController

  include CommonStuff

  def stuff_that_is_different
    # Stuff specific to Bar
    stuff_that_is_the_same
    # More stuff specific to Bar
  end

end


As far as getting the name of controller is concerned you can get it in any controller action by calling the method

 controller_name

and the controller state is available in @controller instance variable to your views, so if you want to access it in your views then you can do

 @controller.controller_name

Now looking at your BusinessHours and StaffHours classes, I would say the best thing to do here to make them polymorphic. The first thing you will achieve here is to get rid of an almost identical table. So check out the rails core polymorphic docs

NOTE: But the has_many_polymorphs mentioned by @amurmann is not yet available in rails core, though you can use it as a plugin. Pratik wrote a blog post about it here

For removing duplicate code from the controller, you can either put that in a module (as @Wayne said) or create a base controller from which your Business and Staff hours controllers inherit all the common functionality. Now the solution totally depends what makes more sense in your application. Personally, I will create a base controller as it is more OO, keep my classes structured and code will not be hidden in some module. But some people may think otherwise.


Have you looked into Single Table Inheritance? I think it's the all around best solution for you here.

Essentially you define an hours table and model, that looks exactly like either staff hours or business hours and includes a string column named type. Then you create subclasses of Hours to define methods and validations specific to Business and Staff hours respectively. You also might want to redefine your association to something more generic. In the example, I've called it keeper, as on one who keeps those hours.

In a nutshell rails is handling all the polymorphic behaviour for you.

Models:

class Hour < ActiveRecord::Base
  # common associations/methods etc go here
end

class BusinessHour < Hour
  belongs_to :keeper, :class_name => "Business"
  #business hour specific logic 
end

class StaffHour < Hour
  belongs_to :keeper, :class_name => "Staff"
  #staff hour specific logic
end

class Business < ActiveRecord::Base
  has_many :business_hours, :foreign_key => :keeper_id
  ...
end

class Staff < ActiveRecord::Base
  has_many :staff_hours, :foreign_key => :keeper_id
  ...
end

Route:

map.resources :business_hours, :controller => "hours", :class => "BusinessHour"
map.resources :staff_hours, :controller => "hours", :class => "StaffHour"

Controller:

class HoursController < ApplicationController
  before_filter :select_class
  def select_class
    @class = Kernel.const_get(params[:class])
  end

  def new
    @hour = @class.new
    ...
  end

  def show 
    @hour = @class.find(params[:id])
    ...
  end

  ...
end

Views will look almost exactly what you have now, but you're going to want some helpers to keep the output straight.

module HourHelper
  def keeper_type
    @class == "BusinessHours" ? "Business" : "Staff"
  end     
end




-form_for(obj) do |f|
  =f.error_messages 
  %p =select keeper_type, :mon_start_time, hours 
    to 
    =select keeper_type, :mon_stop_time, hours

=link_to 'Back', obj_path

You might also want to consider creating a custom form builder to simplify the form creation.


It sounds to me like you might be able to combine both into one resource. Let's just call the resource "hours", for this discussiion. The problem that "hour" might belong to either a business or staff could potentially be solved by making the relation polymorph.

You can for sure solve that relation problem with has_many_polymorphs:

class Hours < ActiveRecord::Base
  has_many_polymorphs :participants, :from => [:staff, :business], :through => :hours_participants
end

This would require a new table and might not be the solution with the best performance. However, it should be a DRY solution.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜