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 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