This is my first time using dataTables. I want to be able to display the records in my Projects table and sort by each field. I'm following this RailCast and used the code provided by them. However, my page loads without errors but the actual records are never loaded into the table--it just says "Processing..." indefinitely. Any ideas what would cause this? Some of my code:
view/projects/new.html.erb (where the index of my application points to, and where I want the table displayed)
<table id="projects" class="display" data-source="<%= projects_url(format: "json") %>">
<thead>
<tr>
<th>File</th>
<th>Author</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
projects.js.coffee
jQuery ->
$("#projects").dataTable
bProcessing: true
bServerSide: true
sAjaxSource: $('#projects').data('source')
"aaSorting": [[ 0, "desc" ]]
projects controller
# GET /projects/new
# GET /projects/new.json
def new
#projects = Project.all
respond_to do |format|
format.html # new.html.erb
format.json { render json: ProjectsDatatable.new(view_context) }
end
end
Projects_datatable.rb
class ProjectsDatatable
delegate :params, :h, :link_to, :number_to_currency, to: :#view
def initialize(view)
#view = view
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: Project.count,
iTotalDisplayRecords: Project.count,
aaData: data
}
end
private
def data
projects.map do |proj|
[
h(proj.filename),
h(proj.author)
]
end
end
def projects
#projects ||= fetch_projects
end
def fetch_projects
projects = Project.order("#{sort_column} #{sort_direction}")
projects = projects.page(page).per_page(per_page)
if params[:sSearch].present?
projects = projects.where("name like :search or category like :search", search: "%#{params[:sSearch]}%")
end
projects
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def sort_column
columns = %w[name category released_on price]
columns[params[:iSortCol_0].to_i]
end
def sort_direction
params[:sSortDir_0] == "desc" ? "desc" : "asc"
end
end
And my projects migration:
class CreateProjects < ActiveRecord::Migration
def change
create_table :projects do |t|
t.string :filename
t.string :location
t.string :author
end
end
end
If anyone has any input I'd be so grateful. thanks.
The answer is certainly a bit late but i can see some mistakes at those lines :
projects = projects.where("name like :search or category like :search", search: "%#{params[:sSearch]}%")
and
columns = %w[name category released_on price]
You have to replace those fields by filename, location, author.
iTotalDisplayRecords: Project.count,
is also wrong, replace it with
iTotalDisplayRecords: projects.total_entries,
Be sure that you have will_paginate
Related
Given that each project has_many :tasks, I hope to render the project.task within the json result.
However, the json output also include a list of individual tasks as part of the result. See below:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
#all = #tasks + #projects
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
per_page: 25 }
end
This means that if I simply include:
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
:include => [:tasks => {:only => :id}],
per_page: 25 }
end
Rails will throw an error of undefined method tasks for Task:0x007fa0ad8d3858 since tasks does not have a task method.
How can I have the project.tasks appear in a json result which also include individual tasks result? Thank you.
Consider using active_model_serializers gem. After installing you can define a serializer for Project model like so:
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :created_at, :tasks
def tasks
object.tasks.map(&:id)
end
end
Note: There might be any attributes you need. It's just an example.
Then you can do:
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
It will return you an array:
[{:id => 1, :created_at => "2017-07-13 08:13:20", tasks => [1, 2, 3, ...]}, ...]
Then for json response you can concat #tasks and serialized_projects:
all_for_json = #tasks + serialized_projects
And finally you can sort it like this:
all_for_json.sort_by { |record| record[:created_at] }.reverse
Note that you should do exactly record[:created_at], because projects are hashes, not active record models.
But I don't think this is a good idea to mix hashes and active record models in one array. So there is another solution.
You can also define a serializer for Task model:
class TaskSerializer < ActiveModel::Serializer
attributes :id, :created_at
end
Note: There might be any attributes you need. It's just an example.
And override code like this:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
respond_to do |format|
format.html do
#all = #tasks + #projects
end
format.json do
serialized_tasks = ActiveModelSerializers::SerializableResource.new(#tasks, each_serializer: TaskSerializer).as_json
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
all_serialized = serialized_tasks + serialized_projects
paginate json: all_serialized.sort_by { |record| record[:created_at] }.reverse, per_page: 25
end
end
To DRY your code, you can put
ActiveModelSerializers::SerializableResource.new(...).as_json
to separate method. For example:
def serialize_collection(collection, each_serializer)
ActiveModelSerializers::SerializableResource.new(collection, each_serializer: each_serializer).as_json
end
And do serializations like this:
serialized_tasks = serialize_collection(#tasks, TaskSerializer)
serialized_projects = serialize_collection(#projects, ProjectSerializer)
Profits of this solution:
You don't mix active record models and hashes in one array.
You can easily define via serializers which attributes and associations to include and set custom names for them.
I have a code in controller:
def latest
#latest_articles = user_signed_in? ? Article.limit(10).order(id: :desc).pluck(:id, :title) : Article.where("status = ?", Article.statuses[:public_article]).limit(10).order(id: :desc).pluck(:id, :title)
render json: #latest_articles
end
How to refactor it to looks elegant?
I tried using lambda:
extract = lambda {|a| a.order(id: :desc).pluck(:id, :title)}
Article.limit(10) {|a| a.extract}
but it returns only Article.limit(10)
UPD: I need to get last 10 of all articles if user is signed in, and last 10 of only public ones if not.
I would create an initial scope, and modify it based on some conditions:
def latest
scope = Article.order(id: :desc)
scope = scope.where(status: Article.statuses[:public_article]) if user_signed_in?
render json: scope.limit(10).pluck(:id, :title)
end
You could refactor as
#lates_articles = Article.all
#lates_articles = #latest_articles.where("status = ?", Article.statuses[:public_article]) unless user_signed_in?
render json: #latest_articles.limit(10).order(id: :desc).pluck(:id, :title)
But it would be better to create model method
class Article < ActiveRecord::Base
...
scope :latest, -> {last(10).order(id: :desc)}
def self.public user_signed
if user_signed
all
else
where("status = ?", statuses[:public_article])
end
end
...
end
Then you would use it like
def latest
render json: Article.public(user_signed_in?).latest.pluck(:id, :title)
end
final version:
def latest
scope = Article.order(id: :desc)
scope = scope.shared unless user_signed_in?
render json: scope.limit(10), except: [:body, :created_at, :updated_at]
end
I am trying to implement a slightly more advanced version of the Railscast covering datatables.
I am able to get the table to work via ajax when the controller is providing the WaresDatatable.new(view_context) response by iteslf which was a win.
However, when I try to expand what I am doing and have the json be nested so that datatables and other functions can use the json response the ajax datatable no longer loads data.
I am relatively new to this and have been banging my head for a while against this seemingly small issue.
Controller:
respond_to do |format|
format.html # show.html.erb
#format.json { render :json => #contributor }
format.json {
render :json => {
:warestable => WaresDatatable.new(view_context),
:contributor => #contributor
}
}
end
wares_datatable.rb (as per railscasts and works when json is unnested)
class WaresDatatable
delegate :params, :h, :link_to, :admin_signed_in?, :edit_ware_path, :current_user, to: :#view
def initialize(view)
#view = view
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: Ware.count,
iTotalDisplayRecords: wares.count,
aaData: data
}
end
private
def data
if admin_signed_in?
wares.map do |product|
[
link_to(product.name, product),
product.origin,
product.chron_range,
product.desc,
link_to("View", product) + " "+link_to("Edit", edit_ware_path(product), :class => 'btn btn-mini') + " " +link_to("Delete", product, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini btn-danger')
]
end
else
wares.map do |product|
[
link_to(product.name, product),
product.origin,
product.chron_range,
product.desc,
link_to("View", product)
]
end
end
end
def wares
#wares ||= fetch_wares
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 1
end
def fetch_wares
contributor = Contributor.find(params[:id])
wares = contributor.wares.order("#{sort_column} #{sort_direction}")
wares = wares.page(page).per_page(per_page)
#wares = wares.user.contributor
if params[:sSearch].present?
wares = wares.where("name like :search or origin like :search or desc like :search", search: "%#{params[:sSearch]}%")
end
wares
end
def sort_column
columns = %w[name origin chron_range desc action]
columns[params[:iSortCol_0].to_i]
end
def sort_direction
params[:sSortDir_0] == "desc" ? "desc" : "asc"
end
end
JS.Coffee file
jQuery ->
$('#warestable').dataTable
sPaginationType: "full_numbers"
bJQueryUI: true
bProcessing: true
bServerSide: true
sAjaxSource: $('#warestable').data('source')
JSON response
{"warestable":{"sEcho":0,"iTotalRecords":22,"iTotalDisplayRecords":1,"aaData":[["crap",null,"old","really crappy condition","View"]]},"contributor":{"ad_board":true,"avatar_content_type":"image/jpeg","avatar_file_name":"success.jpg","avatar_file_size":90652,"avatar_updated_at":"2013-05-04T01:54:52Z","created_at":"2013-05-01T05:19:51Z","email":"jack#jack.com","first_name":"jack","id":1,"last_name":"frost","name":null,"ptrgrph_id":null,"resume_content_type":"image/png","resume_file_name":"Argentina.png","resume_file_size":42260,"resume_updated_at":"2013-05-04T03:17:50Z","searchable":"jack frost jack#jack.com bu","selfdescription":"<p><span style=\"font-family:comic sans ms,cursive\">This is my new website info</span></p>\r\n\r\n<p> </p>\r\n\r\n<p> </p>\r\n\r\n<p><span style=\"font-family:comic sans ms,cursive\">Hhaahaha</span></p>\r\n","university":"bu","updated_at":"2013-05-08T01:58:58Z","user_id":4,"ware_id":null}}
Datatables is able to process nested resources so I am pretty sure its just me. If anyone can point me in the right direction much appreciated.
I know this is an old question but look in to sAjaxDataProp.
It lets you set a custom property for instead of aaData, but I haven't found a way to tell datatables to look for sEcho et al elsewhere.
I have a City model and in city's show action I want to render hotels nearby specific locations in the city. Cities has_many locations; hotels are being searched using Geocoder near method.
To add order functionality I've followed Ryan Bates screencasts #228, but this approach doesn't seem to work with arrays, giving error undefined method `order' for #< Array:0x007f960d003430>
cities_controller.rb
helper_method :sort_column, :sort_direction
def show
session[:search_radius] = 2 if session[:search_radius].blank?
#city = City.find(params[:id])
#locations = #city.locations
#hotels = []
#locations.each do |location|
unless location.longitude.blank? || location.latitude.blank?
center_point = [location.latitude, location.longitude]
box = Geocoder::Calculations.bounding_box(center_point, session[:search_radius])
thotels = Hotel.near(center_point, session[:search_radius]).within_bounding_box(box)
else
thotels = Hotel.near(center_point, session[:search_radius])
end
#hotels += thotels if thotels
#hotels = #hotels.uniq
end
#hotels = #hotels.order(sort_column + " " + sort_direction).paginate(:page => params[:page], :per_page => 5)
#json = #locations.to_gmaps4rails
respond_with #json, :location => city_url
end
private
def sort_column
Hotel.column_names.include?(params[:sort]) ? params[:sort] : "name"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
end
My question is: should I concentrate in converting an array into hash or should I initially create hash of hotels, or maybe find completely different approach to perform sorting?
order is a method used for sorting at the database level. since #hotels is an array, you won't be able to sort using order. Try the following (not tested and you may want to include array pagination if you haven't included it yet)
#hotels = #hotels.sort_by(&:"#{sort_column}")
#hotels = #hotels.reverse if sort_direction == 'DESC'
#hotels = #hotels.paginate(:page => params[:page], :per_page => 5)
If i change the processing from client-side to server-side, i will get all information for the table, but I can't search and sort the columns. But its possible to go to the next page. I have only 2 columns for searching and sorting to test it. Hopefully you can help me.
Database:
t.text "comment"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "source_stock_id"
t.integer "destination_stock_id"
t.integer "order_id"
js.coffee-Code:
jQuery ->
$("#product_relocates_table").dataTable
bProcessing: true
bServerSide: true
sAjaxSource: $('#product_relocates_table').data('source')
"aaSorting": [[ 0, "desc" ]]
Datatable-Code:
class ProductRelocatesDatatable
delegate :params, :h, :link_to, to: :#view
def initialize(view)
#view = view
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: ProductRelocate.count,
iTotalDisplayRecords: product_relocates.total_count,
aaData: data
}
end
private
def data
product_relocates.map do |product_relocate|
[
h(product_relocate.created_at),
h(product_relocate.comment),
h(product_relocate.source_stock),
h(product_relocate.destination_stock),
h(product_relocate.quantity),
link_to('Show', [:admin, product_relocate])
]
end
end
def product_relocates
#product_relocates ||= fetch_product_relocates
end
def fetch_product_relocates
product_relocates = ProductRelocate.order("#{sort_column} #{sort_direction}")
product_relocates = product_relocates.page(page).per(per)
if params[:sSearch].present?
search_string = search_columns.map do |search_column|
"#{search_column} like :search"
end.join(" OR ")
product_relocates = product_relocates.where(search_string, search: "%#{params[:sSearch]}%")
end
product_relocates
end
def page
params[:iDisplayStart].to_i/per + 1
end
def per
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def search_columns
%w[product_relocates.created_at product_relocates.comment]
end
def sort_columns
%w[product_relocates.created_at product_relocates.comment]
end
def sort_column
sort_columns[params[:iSortCol_0].to_i]
end
def sort_direction
params[:sSortDir_0] == "desc" ? "desc" : "asc"
end
end
I refactored a superclass that handles server side multi-column searching and sorting:
https://gist.github.com/2936095
which is derived from:
http://railscasts.com/episodes/340-datatables
class Datatable
delegate :params, :h, :raw, :link_to, :number_to_currency, to: :#view
def initialize(klass,view)
#klass = klass
#view = view
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: #klass.count,
iTotalDisplayRecords: items.total_entries,
aaData: data
}
end
private
def data
[]
end
def items
#items ||= fetch_items
end
def fetch_items
items = filtered_list
items = selected_columns(items)
items = items.order(sort_order)
items = items.page(page).per_page(per_page)
if params[:sSearch].present?
items = items.where(quick_search)
end
items
end
def filtered_list
#klass.all
end
def selected_columns items
items
end
def quick_search
search_for = params[:sSearch].split(' ')
terms = {}
which_one = -1
criteria = search_for.inject([]) do |criteria,atom|
which_one += 1
terms["search#{which_one}".to_sym] = "%#{atom}%"
criteria << "(#{search_cols.map{|col| "#{col} like :search#{which_one}"}.join(' or ')})"
end.join(' and ')
[criteria, terms]
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def columns
[]
end
def sort_order
colnum = 0
sort_by = []
while true
break if !sorted?(colnum)
sort_by << "#{sort_column(colnum)} #{sort_direction(colnum)}"
colnum += 1
end
sort_by.join(", ")
end
def sorted? index=0
!params["iSortCol_#{index}"].nil?
end
def sort_column index=0
index = "iSortCol_#{index}"
columns[params[index].to_i]
end
def sort_direction index=0
index = "sSortDir_#{index}"
params[index] == "desc" ? "desc" : "asc"
end
end