rails-tables 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +1 -1
- data/app/tables/column.rb +20 -8
- data/app/tables/datatable.rb +30 -55
- data/app/tables/datatable/searching.rb +38 -0
- data/app/tables/datatable/sorting.rb +38 -0
- data/lib/rails-tables/version.rb +1 -1
- metadata +4 -2
data/README.md
CHANGED
@@ -12,7 +12,7 @@ A clean jQuery datatables DSL
|
|
12
12
|
[jqd-railscast]: http://railscasts.com/episodes/340-datatables (Episode #340: Datatables)
|
13
13
|
[squeel]: https://github.com/ernie/squeel (Squeel: ActiveRecord 3, improved)
|
14
14
|
|
15
|
-
Version: 0.
|
15
|
+
Version: 0.5.0
|
16
16
|
|
17
17
|
Please refer to the [RailsTables Wiki][wiki] for:
|
18
18
|
|
data/app/tables/column.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
class Column
|
2
2
|
|
3
|
-
attr_accessor :model, :name, :method, :column_source, :render_with, :
|
3
|
+
attr_accessor :model, :name, :method, :column_source, :render_with, :blank_value, :virtual, :sortable, :searchable
|
4
|
+
|
4
5
|
def initialize(model, name, *args)
|
5
6
|
self.model = model
|
6
7
|
self.name = name.to_s
|
@@ -8,18 +9,19 @@ class Column
|
|
8
9
|
attributes = args.pop || {}
|
9
10
|
self.method = attributes.fetch(:method, name).to_s
|
10
11
|
self.column_source = attributes.fetch(:column_source, '').to_s
|
12
|
+
|
13
|
+
virtual = (self.column_source.blank? and not self.model.column_names.include? self.method)
|
14
|
+
self.virtual = attributes.fetch(:virtual, virtual)
|
15
|
+
self.sortable = attributes.fetch(:sortable, !self.virtual)
|
16
|
+
self.searchable = attributes.fetch(:searchable, !self.virtual)
|
17
|
+
|
18
|
+
raise Exception, "Virtual columns are required to supply a render method (render_with: lambda): Column #{self.name}, Model: #{self.model.name}" if virtual and not attributes.has_key?(:render_with)
|
11
19
|
self.render_with = attributes.fetch(:render_with, :default_render)
|
12
|
-
self.sortable = attributes.fetch(:sortable, true)
|
13
|
-
self.searchable = attributes.fetch(:searchable, true)
|
14
20
|
self.blank_value = attributes.fetch(:blank_value, '–')
|
15
21
|
|
16
22
|
define_singleton_method :render do |view, object|
|
17
|
-
related = object
|
18
|
-
self.column_source.split('.').each do |relation|
|
19
|
-
related = related.try(:send, relation)
|
20
|
-
end
|
21
23
|
if self.render_with.kind_of? Symbol
|
22
|
-
content = self.send(self.render_with, view,
|
24
|
+
content = self.send(self.render_with, view, follow_source(object))
|
23
25
|
else
|
24
26
|
content = self.render_with.call(view, object)
|
25
27
|
end
|
@@ -27,6 +29,16 @@ class Column
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
32
|
+
private
|
33
|
+
|
34
|
+
def follow_source(object)
|
35
|
+
related = object
|
36
|
+
self.column_source.split('.').each do |relation|
|
37
|
+
related = related.try(:send, relation)
|
38
|
+
end
|
39
|
+
related
|
40
|
+
end
|
41
|
+
|
30
42
|
def default_render(view, object)
|
31
43
|
property = object.try(:send, self.method)
|
32
44
|
property if not property.nil?
|
data/app/tables/datatable.rb
CHANGED
@@ -1,26 +1,17 @@
|
|
1
1
|
class Datatable
|
2
|
+
include Datatable::Sorting
|
3
|
+
include Datatable::Searching
|
2
4
|
delegate :params, to: 'self.view'
|
3
5
|
|
4
|
-
attr_accessor :name, :model
|
6
|
+
attr_accessor :name, :model, :view, :scopes
|
7
|
+
|
8
|
+
# Called in has_datatable for model
|
5
9
|
def initialize(name, model)
|
6
10
|
self.name = name
|
7
11
|
self.model = model
|
8
12
|
end
|
9
13
|
|
10
|
-
|
11
|
-
def self.source_path=(source)
|
12
|
-
self.source = Rails.application.routes.url_helpers.send(source, format: "json")
|
13
|
-
end
|
14
|
-
|
15
|
-
class_attribute :initial_orderings
|
16
|
-
def self.initial_ordering(orderings)
|
17
|
-
self.set_initial_orderings orderings
|
18
|
-
end
|
19
|
-
def self.set_initial_orderings(orderings)
|
20
|
-
self.initial_orderings = {} if self.initial_orderings.nil?
|
21
|
-
self.initial_orderings.merge! orderings
|
22
|
-
end
|
23
|
-
|
14
|
+
# Render data attributes for table for view
|
24
15
|
def html_data
|
25
16
|
options = {}
|
26
17
|
if self.class.source?
|
@@ -35,7 +26,7 @@ class_attribute :initial_orderings
|
|
35
26
|
options
|
36
27
|
end
|
37
28
|
|
38
|
-
|
29
|
+
# Pass in view and scope table for controller
|
39
30
|
def render_with(view, *args)
|
40
31
|
arguments = args.pop || {}
|
41
32
|
self.view = view
|
@@ -43,6 +34,7 @@ attr_accessor :view, :scopes
|
|
43
34
|
return self
|
44
35
|
end
|
45
36
|
|
37
|
+
# Format this table for controller's response
|
46
38
|
def as_json(options={})
|
47
39
|
{
|
48
40
|
sEcho: params[:sEcho].to_i,
|
@@ -52,47 +44,46 @@ attr_accessor :view, :scopes
|
|
52
44
|
}
|
53
45
|
end
|
54
46
|
|
55
|
-
class_attribute :
|
47
|
+
class_attribute :source, :source_factory
|
48
|
+
# Set source url for this table
|
49
|
+
def self.source_path=(source)
|
50
|
+
self.source_factory = source
|
51
|
+
end
|
52
|
+
def self.source
|
53
|
+
@source ||= Rails.application.routes.url_helpers.send(self.source_factory, format: "json")
|
54
|
+
end
|
55
|
+
|
56
|
+
class_attribute :columns, :column_factory
|
57
|
+
# Allow user defined columns, lazily instanciate later after 'self.model' is defined
|
56
58
|
def self.column(name, *args)
|
57
59
|
arguments = args.pop || {}
|
58
60
|
self.column_factory = [] if self.column_factory.nil?
|
59
61
|
self.column_factory << { name: name, args: arguments }
|
60
62
|
end
|
63
|
+
# Lazily instanciates and caches columns
|
61
64
|
def columns
|
62
65
|
@columns ||= self.column_factory.map{ |new_column| Column.new(self.model, new_column[:name], new_column[:args]) }
|
63
66
|
end
|
64
67
|
|
65
|
-
class_attribute :joins
|
66
|
-
self.joins = []
|
68
|
+
class_attribute :joins
|
69
|
+
self.joins = []
|
70
|
+
# Allow user to explicitly join tables (not sure of use case)
|
67
71
|
def self.join(join)
|
68
72
|
self.joins += [join.to_s]
|
69
73
|
end
|
70
|
-
|
74
|
+
# Deduce joins based on columns and explicitly joined tables
|
71
75
|
def joins
|
72
|
-
@joins ||= (self.columns.map(&:column_source).reject(&:blank?) + self.class.joins).uniq
|
73
|
-
end
|
74
|
-
|
75
|
-
class_attribute :searches
|
76
|
-
self.searches = []
|
77
|
-
def self.search_on(column_source, methods)
|
78
|
-
Array(methods).each do |method|
|
79
|
-
join column_source
|
80
|
-
self.searches += [{column_source: column_source.to_s, method: method.to_s}]
|
81
|
-
end
|
82
|
-
end
|
83
|
-
attr_accessor :searches
|
84
|
-
def searches
|
85
|
-
@searches ||= (
|
86
|
-
self.columns.select(&:searchable).select{|c| c.column_source.present?}.map{|c| {column_source: c.column_source, method: c.method} } + self.class.searches).uniq
|
76
|
+
@joins ||= (self.columns.reject(&:virtual).map(&:column_source).reject(&:blank?) + self.class.joins).uniq
|
87
77
|
end
|
88
78
|
|
89
79
|
private
|
90
80
|
|
81
|
+
# Compose query to fetch objects from database
|
91
82
|
def objects
|
92
83
|
query = self.model.uniq
|
93
84
|
self.joins.each do |join|
|
94
|
-
query = query.joins{ join.split('.').inject(self, :__send__).outer }
|
95
|
-
query = query.includes{ join.split('.').inject(self, :__send__).outer }
|
85
|
+
query = query.joins{ join.split('.').inject((join.present? ? self : nil), :__send__).outer }
|
86
|
+
query = query.includes{ join.split('.').inject((join.present? ? self : nil), :__send__).outer }
|
96
87
|
end
|
97
88
|
if sortable
|
98
89
|
sort_expression = sort
|
@@ -107,25 +98,8 @@ private
|
|
107
98
|
end
|
108
99
|
query = query.paginate(page: page, per_page: per_page)
|
109
100
|
end
|
110
|
-
|
111
|
-
def sortable
|
112
|
-
self.columns.map{ |column| column.sortable }[params[:iSortCol_0].to_i] unless params[:bUseDefaultSort] == 'true'
|
113
|
-
end
|
114
|
-
def sort
|
115
|
-
column = self.columns[params[:iSortCol_0].to_i]
|
116
|
-
direction = params[:sSortDir_0] == "asc" ? 1 : -1
|
117
|
-
Squeel::Nodes::KeyPath.new(column.column_source.split('.') << Squeel::Nodes::Order.new(column.method, direction))
|
118
|
-
end
|
119
|
-
|
120
|
-
def search(terms)
|
121
|
-
terms = terms.split if terms.is_a? String
|
122
|
-
self.searches.map do |search|
|
123
|
-
terms.map do |word|
|
124
|
-
Squeel::Nodes::KeyPath.new(search[:column_source].split('.') << Squeel::Nodes::Stub.new(search[:method])) =~ "%#{word}%"
|
125
|
-
end.compact.inject(&:|)
|
126
|
-
end.compact.inject(&:|)
|
127
|
-
end
|
128
101
|
|
102
|
+
# Pagination doesn't merit it's own module.
|
129
103
|
def page
|
130
104
|
params[:iDisplayStart].to_i/per_page + 1
|
131
105
|
end
|
@@ -133,6 +107,7 @@ private
|
|
133
107
|
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
|
134
108
|
end
|
135
109
|
|
110
|
+
# Generate HTML for each row
|
136
111
|
def data
|
137
112
|
objects.map do |object|
|
138
113
|
self.columns.map{ |column| column.render(self.view, object) }
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Datatable::Searching
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_attribute :searches
|
6
|
+
self.searches = []
|
7
|
+
extend ClassMethods
|
8
|
+
include InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Allow user defined fields to sort on in addition to introspected fields
|
13
|
+
def search_on(column_source, methods)
|
14
|
+
Array(methods).each do |method|
|
15
|
+
join column_source
|
16
|
+
self.searches += [{column_source: column_source.to_s, method: method.to_s}]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
private
|
23
|
+
# Introspect available searches as well as user defined ones
|
24
|
+
def searches
|
25
|
+
@searches ||= (self.columns.select(&:searchable).select{|c| c.column_source.present?}.map{|c| {column_source: c.column_source, method: c.method} } + self.class.searches).uniq
|
26
|
+
end
|
27
|
+
# Build Squeel Stubs for search
|
28
|
+
def search(terms)
|
29
|
+
terms = terms.split if terms.is_a? String
|
30
|
+
self.searches.map do |search|
|
31
|
+
terms.map do |word|
|
32
|
+
Squeel::Nodes::KeyPath.new(search[:column_source].split('.') << Squeel::Nodes::Stub.new(search[:method])) =~ "%#{word}%"
|
33
|
+
end.compact.inject(&:|)
|
34
|
+
end.compact.inject(&:|)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Datatable::Sorting
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_attribute :initial_orderings
|
6
|
+
extend ClassMethods
|
7
|
+
include InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Gotta set these to fight against datatables' default ordering on first column
|
12
|
+
def initial_ordering(orderings)
|
13
|
+
self.set_initial_orderings orderings
|
14
|
+
end
|
15
|
+
def set_initial_orderings(orderings)
|
16
|
+
self.initial_orderings = {} if self.initial_orderings.nil?
|
17
|
+
self.initial_orderings.merge! orderings
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
private
|
24
|
+
# Check if table is sortable
|
25
|
+
def sortable
|
26
|
+
self.columns.map{ |column| column.sortable }[params[:iSortCol_0].to_i] unless params[:bUseDefaultSort] == 'true'
|
27
|
+
end
|
28
|
+
# Find column to search by and create a Squeel Order
|
29
|
+
def sort
|
30
|
+
column = self.columns[params[:iSortCol_0].to_i]
|
31
|
+
if column.sortable
|
32
|
+
direction = params[:sSortDir_0] == "asc" ? 1 : -1
|
33
|
+
Squeel::Nodes::KeyPath.new(column.column_source.split('.') << Squeel::Nodes::Order.new(column.method, direction))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/rails-tables/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-tables
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -117,6 +117,8 @@ extra_rdoc_files: []
|
|
117
117
|
files:
|
118
118
|
- app/assets/javascripts/rails-tables.js.coffee
|
119
119
|
- app/tables/column.rb
|
120
|
+
- app/tables/datatable/searching.rb
|
121
|
+
- app/tables/datatable/sorting.rb
|
120
122
|
- app/tables/datatable.rb
|
121
123
|
- config/routes.rb
|
122
124
|
- lib/rails-tables/engine.rb
|