4
\$\begingroup\$

I'm currently building a text editor using contenteditable and RoR back end. As the editor is WYSIWYG and trying to follow DRY, I used only one file to 3 actions: Show, Edit and New.

Edit and new will pretty much be the same, however, show won't be editable and won't show the Subtitle if it's empty. I was doing it all through JavaScript, but it didn't feel right to me, so I decided to make it using rails.

This is the final result. 0 Javascript, all rails. It is working now, but it's not DRY. There are lots of repeated conditions.

How can this code be more DRY and human-readable?

%section#text-editor-container
  %article#new-text 
    %section#text-header.new-text-container.row{style: ("background: url('#{@text.text_images.last.photo.url}') center center no-repeat transparent; background-size: cover;" unless @text.text_images.empty?)}
      - if (current_page?(edit_text_url(@text)) || current_page?(new_text_url))
        .picture-icon.hidden-xs.col-sm-1.col-md-1.col-lg-1
          = render 'direct_upload', callback: text_cover_url
      %h1#title.col-xs-12.col-sm-11.col-md-11.col-lg-11.placeholdify{class: ('editable' if current_page?(edit_text_url(@text)) || current_page?(new_text_url)), contenteditable: (true if current_page?(edit_text_url(@text)) || current_page?(new_text_url)), data: {placeholder: t(:title)}}= raw(@text.title) unless @text.title.nil?
      - if (((@text.subtitle.nil? || @text.subtitle.empty?) && (current_page?(edit_text_url(@text)) || current_page?(new_text_url))) || !@text.subtitle.empty?)
        %h2#subtitle.col-xs-12.col-sm-11.col-md-11.col-lg-11.placeholdify{class: ('editable' if current_page?(edit_text_url(@text)) || current_page?(new_text_url)), contenteditable: (true if current_page?(edit_text_url(@text)) || current_page?(new_text_url)), data: {placeholder: t(:sub_title)}}= raw(@text.subtitle) unless @text.subtitle.nil?
    %section#text-commands-bar.new-text-container
      -if @text.is_draft
        %span.pull-left= t(:text_still_draft)
      %ul.pull-right.options
        - if (current_page?(new_text_url) || current_page?(edit_text_url(@text)))
          %li.drop_down_menu#menu_categoria.text-center
            = select("text", "category_id", Category.all.collect { |c| [t(c.locale_key.to_sym), c.id]}, {include_blank: t(:select_cat)})
          %li.drop_down_menu#menu_lingua.text-center
            = select("text", "language_id", Language.all.collect { |l| [l.name, l.id] }, {include_blank: t(:select_lan)})
          %li
            %button#publish= t(:publish)
          %li.drop_down_menu#menu_salvar.text-center
            = link_to t(:save_draft), @text, class: 'btn'
        - elsif (!current_page?(new_text_url) && !current_page?(edit_text_url(@text))) && (!@current_user.nil? && @current_user.id == @text.user_id)
          %li 
            = link_to '', edit_text_path(@text), class: 'btn icon-editor edit-icon'
          %li 
            = link_to '', text_path(@text), method: :delete , data: {confirm: t(:exclusion_confirm)},class: 'btn icon-editor delete-icon'

    %section#text-body.new-text-container
      %p#text.placeholdify{class: ('editable' if current_page?(edit_text_url(@text)) || current_page?(new_text_url)),contenteditable: (true if current_page?(edit_text_url(@text)) || current_page?(new_text_url)), data: {placeholder: t(:new_text)}}= @text.description.html_safe unless @text.description.nil?
= hidden_field_tag 'my_text', @text.id
\$\endgroup\$
  • 2
    \$\begingroup\$ Why not use existing text editors? I highly recommend ckeditor: github.com/galetahub/ckeditor \$\endgroup\$ – BroiSatse Jun 5 '14 at 8:05
  • \$\begingroup\$ I would create helper methods that offer abstractions over the methods you're already using. For example, (current_page?(edit_text_url(@text)) || current_page?(new_text_url)) is used twice and could be refactored into a new_or_edit_page? method. \$\endgroup\$ – Nick McCurdy Aug 6 '14 at 6:06
6
\$\begingroup\$

There's two things I usually consider in this kind of situation:

Use custom helpers and partials

The idea here is to break down your view in small components, and have one helper to build each component. Most of the time those helpers will just dispatch and render an appropriate partial :

def wysiwyg_title(text_object)
  if text_object.persisted?
    render 'components/wysiwyg/title/persisted', text: text_object
  else
    render 'components/wysiwyg/title/new_record', text: text_object
  end
end

sometimes you can also DRY it up a little by using 'peacemeal' helpers that only affect parts of a tag :

def wysiwyg_container_classes(text_object)
  classes = ['wysiwyg_container']
  classes << 'wysiwyg_container--draft' unless text_object.persisted?
  classes     
end

(use it like that:)

= content_tag :div, class: wysiwyg_container_classes(@text)

and so on. You get the spirit.

Use presenters

Another option is to use the presenter pattern. The Draper gem, for instance, makes it easy.

The idea here is that instead of passing your model directly to the view, you decorate it with the appropriate presenter. You'll have for instance one presenter for the editable view, and one for the static view. Each presenter will have methods like title, container, etc. that know how to render a component of your view, format the values, etc. your actual view will simply be a layout that arranges the components.

As presenters are objects, you are free to use inheritance / composition / delegation to share some logic, which often helps DRYing the code while keeping it clear and readable.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.