0

I'm a RoR newbie (v3.2.13), and I'm building a new site/application that has a lot of landing pages, divided into 6-8 different layouts. The layouts are using similar elements in a changing order, so I'm using partials and layout inheritance to render each page appropriately.

My problem is that my forms (located at the top of each page) are also inconsistent (for example, while one page might only have a "name" and "email" fields, another will contain an additional "phone" field, a third one might also include a "password", "address" and "country" fields, etc.), and building a different partial for each page seems like a very prodigal approach, DRY wise, not to mention how ineffective it is, especially since I expect a vast amount of new pages in the future.

I've researched the forms concept thoroughly but have yet to come up with a solution that I deem good enough, probably due to me being a newbie. Unfortunately I don't have any code to present, since I waited until I make a decision regarding the optimal approach to start coding.

What I came up with so far is using jQuery and conditionals within Haml, which I understand are both naive approaches, while I read coding each field into its own partial would be considered an anti-pattern (alternatively - NOT the Rails 3 way).

Is there a best practice pertaining to such design issues? Are there any online/other resources I missed that would point me in the right direction?

Thanks!

CodeBender
  • 67
  • 9
  • 1
    Can you give a little more background on what you're trying to do, ultimately? Your design should reflect intention as best as possible, so if these are various permutations of a landing page a solution that shares part of that code makes sense; conversely, if they're 8 pages with completely separate goals (one signs up for a newsletter, another signs in with Facebook, etc) then you're not really duplicating code so much as you're re-using it and there isn't a shared-ancestor (though an abstracted form builder *might* still be appropriate). – coreyward Aug 20 '13 at 16:06
  • @coreyward, thanks for inquiring: there will be a shared ancestor, namely they all are, as you mentioned, various permutations of the same generic landing page (they all share the same footer, header, and logic). To answer your question, all forms are meant to share the same goal, which is to send the user's details to the DB for future communication. Nothing fancy going on there. – CodeBender Aug 20 '13 at 16:21

2 Answers2

3

I would solve this by building a single form with all of the elements, putting it in a partial, and including it on all of the landing pages. Then in the individual stylesheets for each landing page, hide all of the elements that you don't want to use on that particular page.

To solve potential problems with validations (e.g. you need to validate that a password was given when requested) you can create a configuration file (e.g. config/initializers/landing_forms.rb) where you specify which form fields are to be included in each, then generate the appropriate CSS from that file and also use it in your validations, i.e.:

landing_forms.rb

YourInitializer = {
  foo: [:email, :password],
  bar: [:email, :name, :address],
  baz: [:name, :phone]
}.with_indifferent_access.freeze

_form.html.erb

<%= form_for ... do |f| %>
  <%= f.hidden_field_tag :landing_page_id, value: 'foobar' %>
<% end %>

controller

class UsersController < ApplicationController
  def create
    @user = User.new(params[:user])
    User.validate_only requested_fields
    # ...
  end
protected
  def requested_fields
    YourConfig.fetch params[:landing_page_id]
  end
end

model

class User < ActiveRecord::Base
  attr_reader :required_validations
  validate :email, presence: true, if: :email_required?

  def email_required? # feel free to use a bit of metaprogramming to make this simpler
    required_validations.include? :email
  end

  def validate_only(fields)
    @required_validations = fields
  end
end

page_forms.css.erb

/* Hide all non-button inputs by default, then show individually */
<% YourConfig.each_pair do |landing_page_id, fields| %>
  <% fields.each do |field| %>
    #<%= landing_page_id %> form .<% field %> { display: block }
  <% end %>
<% end %>

Hopefully that's enough to get you started!

coreyward
  • 77,547
  • 20
  • 137
  • 166
  • thanks for the long and detailed solution, worked like a charm! I'll come back and vote this one up when I have enough "reputation" to do so. – CodeBender Aug 27 '13 at 07:15
0

How about this sort of structure?

# Controller
@fields = [:name, :country, :phone]

# View
@fields.each do |field|
   render "form/" + field.to_s
end

# form/name.html.erb
<%= text_field(:name) %>
Dan Grahn
  • 9,044
  • 4
  • 37
  • 74
  • Defining the fields in the controller is fishy. Controllers should know as little as possible about what the data is and instead focus on connecting request data with models and renderers/views. – coreyward Aug 20 '13 at 18:32
  • I prefer this to having a separate config file. – Dan Grahn Aug 20 '13 at 18:33
  • @screenmutt, unfortunately I don't have enough "reputation" to vote this one up, but please note this solution also worked for me. I think config file solution is more along the lines of "the Rails Way", so I'd pick that approach first if I were to develop a long-term, high quality app, but would probably go with yours if I was going for a quick and dirty way to get it done. Thanks! – CodeBender Aug 27 '13 at 07:13