Rails Form block in helper - How do i include “Pro

2020-07-20 03:07发布

问题:

I'm trying to build a form block for my liquid theme language. I have based my approach on this answer. How ever the answer seems to be incomplete.

The problem is that protect from forgery and some other methods are unavailable. Causing an error:

Liquid error: undefined method `protect_against_forgery?' for #

This is my code:

   class LiquidFormTag < Liquid::Block

        include ActionView::Context
        include ActionView::Helpers::FormHelper

        def initialize(tag_name, markup, tokens)

            super
        end

        def render(context)
            form_tag("#") do

                super
            end
        end
    end

    Liquid::Template.register_tag('liquid_form', LiquidFormTag)

Does any one know how i add the protect_against_forgery method do this class?

Edit: this is the error output:

Edit 2:

This is the relevant part of my Liquid code:

{% ticket_form %}
    {% for offer in event.offers %}

        <div class="well well-sm">  
            <div class="row">
                <div class="col-xs-3 col-sm-5 col-md-6 col-lg-7">
                    <h5>{{offer.name}}</h5>
                </div>
                <div class="col-xs-9 col-sm-7 col-md-6 col-lg-5 pull-right">
                    <div class="input-group">
                        <span class="input-group-addon">{{offer.price}}</span>
                        <input type="email" class="form-control tickets-count" cols="2" id="exampleInputEmail1" placeholder="0">
                        <span class="input-group-btn">
                            <button type="button" class="btn btn-default"><i class="fa fa-plus"></i></button>
                            <button type="button" class="btn btn-default"><i class="fa fa-minus"></i></button>
                        </span>
                    </div>
                </div>
            </div>
        </div>

    {% endfor %}
{% endticket_form %}

回答1:

I agree to Rodrigo but it will be hard to identify method names that will be deleagated to controller.

That's why I prefer to extend Liquid::Block class and delegate missing methods to controller if responds..

class LiquidFormTag < Liquid::Block
  include ActionView::Context
  include ActionView::Helpers::FormHelper

  attr_reader :controller

  def initialize(tag_name, markup, tokens)
    super
  end

  def render(context)
    @controller = context.registers[:controller]
    form_tag('#') do
      super(context).html_safe
    end
  end

end


Liquid::Block.class_eval do
  # This delegates missing - including private & protected - methods (like protect_against_forgery?) to controller.
  def method_missing(*args)
    begin
      if controller.respond_to?(args.first, true)
        controller.send(args.first)
      else
        super
      end
    rescue
      super
    end
  end
end


回答2:

The method protect_against_forgery came from ActionController::RequestForgeryProtection module. But include this module in class Liquid::Block doesn't seems to be a good practice.

The solution I use is:

class LiquidFormTag < Liquid::Block
  include ActionView::Context
  include ActionView::Helpers::FormHelper

  attr_reader :controller

  def initialize(tag_name, markup, tokens)
    super
  end

  def render(context)
    @controller = context.registers[:controller]
    form_tag('#') do
      super(context).html_safe
    end
  end

  delegate :form_authenticity_token, :request_forgery_protection_token, :protect_against_forgery?, to: :controller

end

Liquid::Template.register_tag 'liquid_form', LiquidFormTag

To this code work, you need to provide current controller to Liquid render method:

class OffersController < ApplicationController

   def create
      @offer = Offer.new
      Liquid::Template.parse(template).render 'offer' => @offer, registers: {controller: self}
   end

end


回答3:

I had the same problem this days, and I solved this way:

class LiquidForm < Liquid::Block
  include ActionView::Context
  include ActionView::Helpers::FormHelper

  attr_reader :csrf_token

  def initialize(tag_name, markup, options)
   super
   @model = markup.strip
  end

  def render(context)
    csrf = context.registers[:csrf_token]

    form_tag "/new_obj", authenticity_token: false do
      "#{hidden_field_tag('authenticity_token', csrf)}
       #{super(context)}".html_safe
    end

  end
end

Now I needed to generate the security token from my App. I investigated that inside the form_tag I could called the method form_authenticity_token that is available from the ActionController::RequestForgeryProtection module. Based on this answer.

This form_authenticity_token only creates a secure random key, you can see it in form_authenticity_token

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 249
  def form_authenticity_token
    session[:_csrf_token] ||= SecureRandom.base64(32)
  end

As we cannot call this inside our LiquidForm class. We need to generate the token manually exactly like this method in our Liquid render method. This can be in your view or controller. Example:

=Liquid::Template.parse(template).render('offer' => @offer, registers: {:csrf_token => session[:_csrf_token] ||= SecureRandom.base64(32)})