rails-tables 0.4.4 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|