jbox-web / ajax-datatables-rails

A wrapper around DataTable's ajax methods that allow synchronization with server-side pagination in a Rails app
MIT License
585 stars 228 forks source link

advanced sample doesn't work #327

Closed epipheus closed 4 years ago

epipheus commented 5 years ago

Rails version: 5.2.3 Ruby version:2.0.4

using concerns with post like the sample says ends up with a clash with #show.

routes.rb

require 'sidekiq/web'

Rails.application.routes.draw do
    concern :with_datatable do
               post 'datatable', on: :collection
    end
    resources :organizations, concerns: [:with_datatable]
    mount Sidekiq::Web, at: "/ninjas"

end

These are my routes:

  datatable_organizations POST   /organizations/datatable(.:format)                                                       organizations#datatable
            organizations GET    /organizations(.:format)                                                                 organizations#index
                          POST   /organizations(.:format)                                                                 organizations#create
         new_organization GET    /organizations/new(.:format)                                                             organizations#new
        edit_organization GET    /organizations/:id/edit(.:format)                                                        organizations#edit {:id=>/[0-9]+/}
             organization GET    /organizations/:id(.:format)                                                             organizations#show {:id=>/[0-9]+/}
                          PATCH  /organizations/:id(.:format)                                                             organizations#update {:id=>/[0-9]+/}
                          PUT    /organizations/:id(.:format)                                                             organizations#update {:id=>/[0-9]+/}
                          DELETE /organizations/:id(.:format)                                                             organizations#destroy {:id=>/[0-9]+/}
              sidekiq_web        /ninjas                                                                                  Sidekiq::Web
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create

Navigating to the route, /organizations/datatables.json produces this error: OrganizationsController#show is missing a template for this request format and variant. It thinks /organizations/datatable.json is a match for /organizations/:id/show despite having a clear route for /organizations/datatable(.:format)

Here's my controller:

class OrganizationsController < ApplicationController
    def index
        @title              = "Organizations"
        @page_description         = "Organization data warehouse"
        @page_icon          = "institution"
        @organization       = Organization.new
        @load               = {data_table: true}

      respond_to do |format|
        format.html
        format.json { render json: OrganizationDatatable.new(params) }
      end

    end
    def datatable
        respond_to do |format|
            format.json { render json: OrganizationDatatable.new(params) }
        end
    end
    def get_raw_records
        Organization.all
    end
    def create

    end
    def edit

    end
    def destroy

    end
    def show

    end
    def update

    end
    def new

    end
end
epipheus commented 5 years ago

full error:

ActionController::UnknownFormat - OrganizationsController#show is missing a template for this request format and variant.

request.formats: ["text/html"]
request.variant: []

NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
epipheus commented 5 years ago

When I tried verifying this it was via browser so not a POST. Only thing keeping me from closing this is that I'm still not out of the woods. The POST is failing for CSRF reasons. Any way to pass CSRF token in the post?

epipheus commented 5 years ago

OK. Figured it out, closing this.

epipheus commented 5 years ago

Actually, the title is that the advance sample doesn't work. And that's true.

The ajax portion of that sample should be replaced with the following snippet:

    "ajax": {
      "url": "/blahblah/datatable.json", // or data-source
      "type": 'POST',
      "beforeSend": function (xhr) {
          xhr.setRequestHeader('X-CSRF-Token', token)
        }
    },

and perhaps add a few more options

jQuery(document).ready(function() {
  var table = $('#blahblah-datatable');
  var token = $('meta[name=csrf-token]').attr('content');
  table.DataTable({
    "sDom": "<t><'row'<p i>>",
    "processing": true,
    "serverSide": true,
    "ajax": {
      "url": "/blahblah/datatable.json", // or data-source
      "type": 'POST',
      "beforeSend": function (xhr) {
          xhr.setRequestHeader('X-CSRF-Token', token)
        }
    },
    "pagingType": "full_numbers",
    "destroy": true,
    "scrollCollapse": true,
    "processing": true,
    "serverSide": true,
    "oLanguage": {
        "sLengthMenu": "_MENU_ ",
        "sInfo": "Showing <b>_START_ to _END_</b> of _TOTAL_ entries"
    },
    "columns": [
      {"data": "blah"},,
      {"data": "blah"},,
      {"data": "blah"},
      {"data": "blah"}
    ],
    "iDisplayLength": 50
  });
shadowreplicant commented 4 years ago

I think cleaner code is to use headers option

    "ajax": {
      "url": "/blahblah/datatable.json", // or data-source
      "type": 'POST',
      "headers": { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') },
    },

based on https://datatables.net/forums/discussion/31574/adding-a-http-header-to-the-ajax-request

n-rodriguez commented 4 years ago

Weird... It should works out of the box. It seems you don't use UJS : https://guides.rubyonrails.org/security.html#csrf-countermeasures

By default, Rails includes an unobtrusive scripting adapter, which adds a header called X-CSRF-Token with the security token on every non-GET Ajax call. Without this header, non-GET Ajax requests won't be accepted by Rails.