The main goal: Allow plugins, gems to add additional form fields to predefined forms.
For example, application has a devise login form:
<%= form_for(resource, :as => resource_name, ...) do |f| %>
<%= devise_error_messages! %>
...
<% end %>
Marketing department wants to start a campaign for the next 2 days (ex: register with a promo code and get X bonus points). So we need to add an additional promo code
field to ALL our registration forms.
Is there a way to add an extra field to the form from my rails-plugin/railtie, and define a on_submit
callback method (to take action on my additional field data)?
Benefits:
Looked at ActionView code, and it seems there is no built in way of doing it. What are your thoughts?
NOTE: Drupal's form_alter
hooks are a great example.
First of all, your idea to isolate this code in a gem/railtie/engine is excellent. I think your best bet may be to monkey patch the form_for method and stick in the extra field. Regarding the on-submit trigger, if you're on Rails 3.1 and are using the asset pipeline, you can have the gem serve javascript as well, although that would require a small change in your application.js
to require the gem's js file, e.g. require 'promo/application.js
, if the gem were called "promo".
Take a look at the docs for Customized Form Builder
Here's some rough idea how this could work, although I haven't tried this code. I'd put this in the promo.rb file that sub-classes Railtie
or Engine
.
ActiveSupport.on_load(:action_view) do
module ActionView
module Helpers
module FormHelper
extend ActiveSupport::Concern
included do
alias_method_chain :form_for, :promo_code
end
module InstanceMethods
def form_for_with_promo_code(record, options = {}, &proc)
output = form_for_without_promo_code(record, options.merge(builder: FormBuilderWithPromoCode), proc)
# See file: actionpack-3.1.3/lib/action_view/helpers/form_helper.rb for details
# the output will have "</form>" as the last thing, strip that off here and inject your input field
promo_field = content_tag :input, name: 'promo_code' # you can also run this through the proc if you want access to the object
output.sub(%r{</form>$},promo_field+'</form>')
end
end
end
end
end
end
Down the road, esp. if your marketing department may run more campaigns, you may even want to change the app's forms, to point to a specific builder that you can override from a gem without the monkey patching here.
Idea in steps:
1) Define a model such as AdditionalField (id, field_name, field_type, default_value, is_required)
2) then create a function like:
def self.for_form(my_form_name = nil)
if my_form_name.nil?
self.all
else
self.find(:all, :contitions => {:form_type => my_form_name.type} # or whatever selection criteria
end
3) then you can iterate over the found AdditionalFields and build the correct field types as needed.
I used this solution for a comparison website where they needed to configure the questionnaires for each different comparison type.
Here's the render code I used, you'll need to amend it to suit your situation. relationships are:
convention -< booking >- user
convention -< convention_question
booking -< guests
guest -< guest_answers
QuestionsHelper
def render_guest_questions(guest, convention_question)
fields_for "booking[guest_answer_attributes][]", convention_question do |m|
case convention_question.display_type
when "Text"
'<td>' + text_field_tag("booking[guest_answer_attributes][convention_question_#{guest.id}_#{convention_question.id}]") + '</td>'
when "Boolean"
'<td>' + hidden_field_tag("booking[guest_answer_attributes][convention_question_#{guest.id}_#{convention_question.id}]", "No") + check_box_tag("booking[guest_answer_attributes][convention_question_#{guest.id}_#{convention_question.id}]", "Yes") + '</td>'
end
end
end
Controller
# TURN GUEST/QUESTIONS INTO guest answers
if params[:booking] && !params[:booking].blank? && !params[:booking][:guest_answer_attributes].blank?
params[:booking][:guest_answer_attributes].each do |k,v|
handle_answers(k, v)
end
end
def handle_answers(k, v)
x = k.mb_chars.split(/_/)
g_id = x[2]
q_id = x[3]
item = GuestAnswer.find_or_create_by_guest_id_and_convention_question_id(
{:guest_id => g_id,
:convention_question_id => q_id,
:answer => v})
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With