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 %}
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
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
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)})