DRY a large chunk of common code with two vastly different uses
I have a 'widget' that comprises an html/css block of code. It is a type of data layout, which I call the 'stack'.
The stack has bits of .erb
(Ruby on Rails) embedded in it, which enters the data for each user.
I need to include this stack in multiple places, where it needs to represent different data from different models.
So, one stack might contain a field called @company.name
and the other stack might contain @project.name || "Unidentified Project"
.
How does one refactor / organize this situation? Options that I can see:
- Have two separate
stacks
, which would in开发者_JAVA百科troduce redundancy and inconsistency, but would be an obvious answer to the problem without limit to scenario-specific customization. - Include
if
statements for every data point to test which circumstance the stack is being used for, but this is very code-ugly and unsustainably complicated for more than 2 stacks. - Some unknown unknown.
How would you tackle this?
One simple way would be to write the erb in a generic way so that it works for either a project or a company for example, in your Project model you could put:
def display_name
name || "Unidentified Project"
end
Then in your Company model put:
def display_name
name
end
When you render the ERB, pass in a variable with some generic name like main_object
and call its display_name
function. The ERB code would not know or care what class main_object
is:
<%= main_object.display_name %>
If it bothers you to put display-related functions like display_name
in your models, you could use the Presenter pattern. A present is basically a plain-old ruby object that you create from your model(s) and then pass to the view. I saw a good talk on this pattern by Jeff Casimir called "Fat Models Aren't Enough" and the slides are here:
http://en.oreilly.com/rails2011/public/schedule/detail/18514
David's solutions are good. In some situations you may also consider helpers or partials (e.g. if you want to include complex html). For example:
Helper
def display_name(object)
if object.respond_to? :name and object.name
object.name
else
if object.class.respond_to? :human_name
"Unidentified #{object.class.human_name}"
else
"Unidentified #{object.class.name}"
end
end
end
Partial
<%= render :partial => "stack/name/#{object.class.underscore}", :locals => {object.class.underscore => object} %>
With any complex ERB in app/views/stack/name/_project.html.erb
:
<label style="<%= "background-color: red;" if project.name.blank? %>">
<%= project.name || "Unidentified Project" %>
</label>
精彩评论