module Effective class Datatable attr_accessor :total_records, :display_records, :view, :attributes delegate :render, :controller, :link_to, :mail_to, :number_to_currency, :to => :@view include Effective::EffectiveDatatable::Dsl extend Effective::EffectiveDatatable::Dsl::ClassMethods include Effective::EffectiveDatatable::Ajax include Effective::EffectiveDatatable::Options include Effective::EffectiveDatatable::Rendering def initialize(*args) if args.present? && args.first != nil raise "#{self.class.name}.new() can only be initialized with a Hash like arguments" unless args.first.kind_of?(Hash) args.first.each { |k, v| self.attributes[k] = v } end initialize_datatable # This creates @table_columns based on the DSL datatable do .. end block initialize_options # This normalizes all the options unless active_record_collection? || array_collection? raise "Unsupported collection type. Should be ActiveRecord class, ActiveRecord relation, or an Array of Arrays [[1, 'something'], [2, 'something else']]" end # Any pre-selected search terms should be assigned now search_terms.each { |column, term| self.send("#{column}=", term) } end def table_columns @table_columns end # Any attributes set on initialize will be echoed back and available to the class def attributes @attributes ||= HashWithIndifferentAccess.new() end def to_key; []; end # Searching & Filters # Instance method. In Rails 4.2 this needs to be defined on the instance, before it was on the class def model_name # Searching & Filters @model_name ||= ActiveModel::Name.new(self.class) end def self.model_name # Searching & Filters @model_name ||= ActiveModel::Name.new(self) end def to_param @to_param ||= self.class.name.underscore.sub('effective/datatables/', '') end def collection raise "You must define a collection. Something like an ActiveRecord User.all or an Array of Arrays [[1, 'something'], [2, 'something else']]" end def collection_class @collection_class ||= (collection.respond_to?(:klass) ? collection.klass : self.class) end def to_json raise 'Effective::Datatable to_json called with a nil view. Please call render_datatable(@datatable) or @datatable.view = view before this method' unless view.present? @json ||= { :draw => (params[:draw] || 0), :data => (table_data || []), :recordsTotal => (total_records || 0), :recordsFiltered => (display_records || 0) } end def present? total_records.to_i > 0 end def empty? total_records.to_i == 0 end def total_records @total_records ||= ( if active_record_collection? # https://github.com/rails/rails/issues/15331 if collection_class.connection.respond_to?(:unprepared_statement) collection_sql = collection_class.connection.unprepared_statement { collection.to_sql } (collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection_sql}) AS datatables_total_count").first['count'] rescue 1).to_i else (collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection.to_sql}) AS datatables_total_count").first['count'] rescue 1).to_i end else collection.size end ) end def view=(view_context) @view = view_context @view.formats = [:html] # 'Just work' with attributes @view.class.send(:attr_accessor, :attributes) @view.attributes = self.attributes # Delegate any methods defined on the datatable directly to our view @view.class.send(:attr_accessor, :effective_datatable) @view.effective_datatable = self (self.class.instance_methods(false) - [:collection, :search_column]).each do |view_method| @view.class_eval { delegate view_method, :to => :@effective_datatable } end # Clear the search_terms memoization @search_terms = nil @order_name = nil @order_direction = nil end protected def params view.try(:params) || HashWithIndifferentAccess.new() end def table_tool @table_tool ||= ActiveRecordDatatableTool.new(self, table_columns.select { |_, col| col[:array_column] == false }) end def array_tool @array_tool ||= ArrayDatatableTool.new(self, table_columns.select { |_, col| col[:array_column] == true }) end def active_record_collection? collection.ancestors.include?(ActiveRecord::Base) rescue false end def array_collection? collection.kind_of?(Array) && collection.first.kind_of?(Array) end end end