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 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.4.4
15
+ Version: 0.5.0
16
16
 
17
17
  Please refer to the [RailsTables Wiki][wiki] for:
18
18
 
@@ -1,6 +1,7 @@
1
1
  class Column
2
2
 
3
- attr_accessor :model, :name, :method, :column_source, :render_with, :sortable, :searchable, :blank_value
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, related)
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?
@@ -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
- class_attribute :source
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
- attr_accessor :view, :scopes
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 :columns, :column_factory
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
- attr_accessor :joins
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
@@ -1,3 +1,3 @@
1
1
  module RailsTables
2
- VERSION = "0.4.4"
2
+ VERSION = "0.5.0"
3
3
  end
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.4
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-28 00:00:00.000000000 Z
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