rails-tables 0.6.6 → 0.7.0.rc1
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/assets/javascripts/rails-tables.js.coffee +0 -1
- data/lib/rails-tables.rb +1 -0
- data/lib/rails-tables/datatable.rb +15 -10
- data/lib/rails-tables/datatable/column.rb +72 -77
- data/lib/rails-tables/datatable/column/renderers.rb +38 -0
- data/lib/rails-tables/datatable/searching.rb +4 -4
- data/lib/rails-tables/datatable/sorting.rb +1 -1
- data/lib/rails-tables/dsl_proxy.rb +120 -0
- data/lib/rails-tables/version.rb +1 -1
- metadata +7 -5
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.7.0.rc1
|
16
16
|
|
17
17
|
Please refer to the [RailsTables Wiki][wiki] for:
|
18
18
|
|
@@ -6,7 +6,6 @@ $ ->
|
|
6
6
|
|
7
7
|
@rails_tables = {}
|
8
8
|
@rails_tables.columns = (datatable)->
|
9
|
-
console.log $(datatable).data().order_column, $(datatable).data().order_direction
|
10
9
|
asSorting: [ $(datatable).data().order_direction ], aTargets: [ $(datatable).data().order_column ]
|
11
10
|
@rails_tables.params = (datatable) ->
|
12
11
|
(aoData) ->
|
data/lib/rails-tables.rb
CHANGED
@@ -6,7 +6,7 @@ class RailsTables::Datatable
|
|
6
6
|
include RailsTables::Searching
|
7
7
|
delegate :params, to: 'self.view'
|
8
8
|
|
9
|
-
attr_accessor :name, :root, :model, :view
|
9
|
+
attr_accessor :name, :root, :model, :view, :locals
|
10
10
|
|
11
11
|
# Called in has_datatable for model, or on an ActiveRecord::Relation in method_missing
|
12
12
|
def initialize(name, model)
|
@@ -26,7 +26,7 @@ class RailsTables::Datatable
|
|
26
26
|
options[:source] = self.class.source
|
27
27
|
end
|
28
28
|
if self.initial_order.present?
|
29
|
-
options[:order_column] = self.columns.select{|c|c.name==self.class.initial_order.first[0].to_s}.first.
|
29
|
+
options[:order_column] = self.columns.select{|c|c.name==self.class.initial_order.first[0].to_s}.first.index
|
30
30
|
options[:order_direction] = self.class.initial_order.first[1]
|
31
31
|
end
|
32
32
|
options[:unsorted] = 'true'
|
@@ -34,8 +34,9 @@ class RailsTables::Datatable
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# Pass in view and scope table for controller
|
37
|
-
def render_with(view)
|
37
|
+
def render_with(view, locals={})
|
38
38
|
self.view = view
|
39
|
+
self.locals = locals
|
39
40
|
return self
|
40
41
|
end
|
41
42
|
|
@@ -60,14 +61,18 @@ class RailsTables::Datatable
|
|
60
61
|
|
61
62
|
class_attribute :columns, :column_factory
|
62
63
|
# Allow user defined columns, lazily instanciate later after 'self.root' is defined
|
63
|
-
def self.column(name,
|
64
|
-
arguments = args.pop || {}
|
64
|
+
def self.column(name, options={}, &block)
|
65
65
|
self.column_factory = [] if self.column_factory.nil?
|
66
|
-
self.column_factory << { name: name.to_s,
|
66
|
+
self.column_factory << { name: name.to_s, block: block }.merge(options)
|
67
67
|
end
|
68
68
|
# Lazily instanciates and caches columns
|
69
69
|
def columns
|
70
|
-
@columns ||= self.column_factory.map.with_index
|
70
|
+
@columns ||= self.column_factory.map.with_index do |new_column, index|
|
71
|
+
new_column[:index] = index
|
72
|
+
new_column[:table] = self
|
73
|
+
new_column[:model] = self.model
|
74
|
+
RailsTables::ColumnBuilder.define(new_column)
|
75
|
+
end
|
71
76
|
end
|
72
77
|
|
73
78
|
class_attribute :joins
|
@@ -76,9 +81,9 @@ class RailsTables::Datatable
|
|
76
81
|
def self.join(join)
|
77
82
|
self.joins += [join.to_s]
|
78
83
|
end
|
79
|
-
# Deduce joins based on columns and explicitly joined tables
|
84
|
+
# Deduce joins based on columns and explicitly joined tables
|
80
85
|
def joins
|
81
|
-
@joins ||= (self.columns.reject(&:virtual).map(&:column_source).reject(&:blank?) + self.class.joins).uniq
|
86
|
+
@joins ||= (self.columns.reject(&:virtual).map(&:column_source).reject(&:blank?).map(&:to_s) + self.class.joins).uniq
|
82
87
|
end
|
83
88
|
|
84
89
|
private
|
@@ -106,7 +111,7 @@ private
|
|
106
111
|
# Generate HTML for each row
|
107
112
|
def data
|
108
113
|
objects.map do |object|
|
109
|
-
self.columns.map{ |column| column.render(
|
114
|
+
self.columns.map{ |column| column.render(object) }
|
110
115
|
end
|
111
116
|
end
|
112
117
|
|
@@ -1,93 +1,88 @@
|
|
1
|
-
|
1
|
+
require "rails-tables/datatable/column/renderers"
|
2
|
+
module RailsTables
|
3
|
+
mattr_accessor :column_attributes
|
4
|
+
self.column_attributes = [:name, :index, :table, :model, :method_name, :column_source, :follow_source, :renderer, :blank_value, :virtual, :sortable, :searchable]
|
2
5
|
|
3
|
-
|
6
|
+
class ColumnBuilder
|
7
|
+
attr_reader :column
|
8
|
+
def self.define(column)
|
9
|
+
block = column.delete(:block)
|
10
|
+
@builder = self.new(column)
|
11
|
+
DslProxy.exec(@builder, &block) if block
|
12
|
+
@builder.column
|
13
|
+
end
|
14
|
+
def initialize(column)
|
15
|
+
@column = Column.from_hash(column)
|
16
|
+
end
|
17
|
+
RailsTables.column_attributes.reject{|c|c==:renderer}.each do |attr|
|
18
|
+
define_method attr do |value|
|
19
|
+
@column.send("#{attr}=".to_sym, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
def renderer(value=nil, &block)
|
23
|
+
@column.renderer = block || value || nil
|
24
|
+
end
|
25
|
+
end
|
4
26
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
self.
|
9
|
-
|
27
|
+
Column = Struct.new(*RailsTables.column_attributes) do
|
28
|
+
include RailsTables::Renderers
|
29
|
+
# Arrange a hash into properly ordered array of args for this Struct
|
30
|
+
def self.from_hash hash
|
31
|
+
self[*hash.values_at(*self.members.map {|m| m.to_sym})]
|
32
|
+
end
|
33
|
+
# Set some defaults
|
34
|
+
def initialize(*args)
|
35
|
+
super
|
36
|
+
self[:method_name] = name.to_s if method_name.nil?
|
37
|
+
self[:column_source] = '' if column_source.nil?
|
38
|
+
self[:renderer] = :default_renderer if renderer.nil?
|
39
|
+
self[:follow_source] = !self[:renderer].is_a?(Proc) if follow_source.nil?
|
40
|
+
self[:blank_value] = '—' if blank_value.nil?
|
41
|
+
self[:virtual] = false if virtual.nil?
|
42
|
+
self[:sortable] = !virtual if sortable.nil?
|
43
|
+
self[:searchable] = !virtual if searchable.nil?
|
10
44
|
|
11
|
-
|
12
|
-
|
13
|
-
|
45
|
+
define_singleton_method :render do |object|
|
46
|
+
render_method = renderer.is_a?(Proc) ? renderer : method(renderer)
|
47
|
+
object = follow_source_on(object) if follow_source
|
48
|
+
content = self.instance_exec object, &render_method
|
49
|
+
content.present? ? content.to_s.html_safe : blank_value
|
50
|
+
end
|
51
|
+
end
|
14
52
|
|
15
|
-
|
16
|
-
self.virtual = attributes.fetch(:virtual, virtual)
|
17
|
-
self.sortable = attributes.fetch(:sortable, !self.virtual)
|
18
|
-
self.searchable = attributes.fetch(:searchable, !self.virtual)
|
53
|
+
private
|
19
54
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
55
|
+
def follow_source_on(object)
|
56
|
+
related = object
|
57
|
+
column_source.to_s.split('.').each do |relation|
|
58
|
+
related = related.try(:send, relation)
|
59
|
+
end
|
60
|
+
related
|
24
61
|
end
|
25
62
|
|
26
|
-
|
27
|
-
|
63
|
+
def default_renderer(object)
|
64
|
+
object.send(method_name) if object.respond_to? method_name
|
65
|
+
end
|
28
66
|
|
29
|
-
|
30
|
-
if
|
31
|
-
|
67
|
+
def respond_to?(sym)
|
68
|
+
if table.view.respond_to? sym
|
69
|
+
true
|
70
|
+
elsif table.locals.has_key? sym
|
71
|
+
true
|
32
72
|
else
|
33
|
-
|
73
|
+
super
|
34
74
|
end
|
35
|
-
content.present? ? content.to_s.html_safe : self.blank_value
|
36
75
|
end
|
37
|
-
end
|
38
76
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
77
|
+
def method_missing(sym, *args)
|
78
|
+
if table.view.respond_to? sym
|
79
|
+
table.view.send(sym, *args)
|
80
|
+
elsif table.locals.has_key? sym
|
81
|
+
table.locals[sym]
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
45
85
|
end
|
46
|
-
related
|
47
|
-
end
|
48
|
-
|
49
|
-
def default_render(view, object)
|
50
|
-
property = object.try(:send, self.method)
|
51
|
-
property if not property.nil?
|
52
|
-
end
|
53
|
-
|
54
|
-
def self_referential_link(view, object)
|
55
|
-
property = object.try(:send, self.method)
|
56
|
-
view.link_to property, object if not property.nil?
|
57
|
-
end
|
58
|
-
def related_link(view, object)
|
59
|
-
property = object.try(:send, self.method)
|
60
|
-
view.link_to property, object if not property.nil?
|
61
|
-
end
|
62
|
-
def related_link_list(view, objects)
|
63
|
-
objects.reject(&:blank?).map{ |object| related_link(view, object).strip }.join(', ') if not objects.nil?
|
64
|
-
end
|
65
86
|
|
66
|
-
def time(view, object)
|
67
|
-
property = object.try(:send, self.method)
|
68
|
-
property.strftime("%I:%M%p") if not property.nil?
|
69
|
-
end
|
70
|
-
def date(view, object)
|
71
|
-
property = object.try(:send, self.method)
|
72
|
-
property.strftime("%m/%d/%Y") if not property.nil?
|
73
87
|
end
|
74
|
-
def datetime(view, object)
|
75
|
-
property = object.try(:send, self.method)
|
76
|
-
property.strftime("%m/%d/%Y at %I:%M%p") if not property.nil?
|
77
|
-
end
|
78
|
-
|
79
|
-
def currency(view, object)
|
80
|
-
property = object.try(:send, self.method)
|
81
|
-
view.number_to_currency(property.to_f) if not property.nil?
|
82
|
-
end
|
83
|
-
def phone(view, object)
|
84
|
-
property = object.try(:send, self.method)
|
85
|
-
view.number_to_phone(property.to_f) if not property.nil?
|
86
|
-
end
|
87
|
-
|
88
|
-
def truncate(view, object)
|
89
|
-
property = object.try(:send, self.method)
|
90
|
-
view.truncate(property, 50) if not property.nil?
|
91
|
-
end
|
92
|
-
|
93
88
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RailsTables
|
2
|
+
module Renderers
|
3
|
+
def link_to_object(object)
|
4
|
+
property = object.try(:send, method_name)
|
5
|
+
link_to property, object if not property.nil?
|
6
|
+
end
|
7
|
+
def link_to_objects(objects)
|
8
|
+
objects.reject(&:blank?).map{ |object| link_to_object(object).strip }.join(', ') if not objects.nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
def time(object)
|
12
|
+
property = object.try(:send, method_name)
|
13
|
+
property.strftime("%I:%M%p") if not property.nil?
|
14
|
+
end
|
15
|
+
def date(object)
|
16
|
+
property = object.try(:send, method_name)
|
17
|
+
property.strftime("%m/%d/%Y") if not property.nil?
|
18
|
+
end
|
19
|
+
def datetime(object)
|
20
|
+
property = object.try(:send, method_name)
|
21
|
+
property.strftime("%m/%d/%Y at %I:%M%p") if not property.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def currency(object)
|
25
|
+
property = object.try(:send, method_name)
|
26
|
+
number_to_currency(property.to_f) if not property.nil?
|
27
|
+
end
|
28
|
+
def phone(object)
|
29
|
+
property = object.try(:send, method_name)
|
30
|
+
number_to_phone(property.to_f) if not property.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def truncate(object)
|
34
|
+
property = object.try(:send, method_name)
|
35
|
+
truncate(property, 50) if not property.nil?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -10,9 +10,9 @@ module RailsTables::Searching
|
|
10
10
|
module ClassMethods
|
11
11
|
# Allow user defined fields to sort on in addition to introspected fields
|
12
12
|
def search_on(column_source, methods)
|
13
|
-
Array(methods).each do |
|
13
|
+
Array(methods).each do |method_name|
|
14
14
|
join column_source
|
15
|
-
self.searches += [{column_source: column_source.to_s,
|
15
|
+
self.searches += [{column_source: column_source.to_s, method_name: method_name.to_s}]
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -29,7 +29,7 @@ private
|
|
29
29
|
def searchables
|
30
30
|
searches = self.columns.
|
31
31
|
select(&:searchable).
|
32
|
-
map{ |c| {column_source: c.column_source,
|
32
|
+
map{ |c| {column_source: c.column_source, method_name: c.method_name} }
|
33
33
|
searches += self.class.searches
|
34
34
|
@searches ||= searches.uniq
|
35
35
|
end
|
@@ -38,7 +38,7 @@ private
|
|
38
38
|
terms = terms.split if terms.is_a? String
|
39
39
|
searchables.map do |search|
|
40
40
|
terms.map do |word|
|
41
|
-
Squeel::Nodes::KeyPath.new(search[:column_source].split('.') << Squeel::Nodes::Stub.new(search[:
|
41
|
+
Squeel::Nodes::KeyPath.new(search[:column_source].to_s.split('.') << Squeel::Nodes::Stub.new(search[:method_name].to_s)) =~ "%#{word}%"
|
42
42
|
end.compact.inject(&:|)
|
43
43
|
end.compact.inject(&:|)
|
44
44
|
end
|
@@ -34,7 +34,7 @@ private
|
|
34
34
|
direction = params[:sSortDir_0] == "asc" ? 1 : -1
|
35
35
|
end
|
36
36
|
if column.sortable
|
37
|
-
Squeel::Nodes::KeyPath.new(column.column_source.split('.') << Squeel::Nodes::Order.new(Squeel::Nodes::Stub.new(column.
|
37
|
+
Squeel::Nodes::KeyPath.new(column.column_source.to_s.split('.') << Squeel::Nodes::Order.new(Squeel::Nodes::Stub.new(column.method_name.to_s), direction))
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class DslProxy < BasicObject
|
2
|
+
|
3
|
+
# Pass in a builder-style class, or other receiver you want set as "self" within the
|
4
|
+
# block, and off you go. The passed block will be executed with all
|
5
|
+
# block-context local and instance variables available, but with all
|
6
|
+
# method calls sent to the receiver you pass in. The block's result will
|
7
|
+
# be returned.
|
8
|
+
#
|
9
|
+
# If the receiver doesn't respond_to? a method, any missing methods
|
10
|
+
# will be proxied to the enclosing context.
|
11
|
+
def self.exec(receiver, &block) # :yields: receiver
|
12
|
+
# Find the context within which the block was defined
|
13
|
+
context = ::Kernel.eval('self', block.binding)
|
14
|
+
|
15
|
+
# Create or re-use our proxy object
|
16
|
+
if context.respond_to?(:_to_dsl_proxy)
|
17
|
+
# If we're nested, we don't want/need a new dsl proxy, just re-use the existing one
|
18
|
+
proxy = context._to_dsl_proxy
|
19
|
+
else
|
20
|
+
# Not nested, create a new proxy for our use
|
21
|
+
proxy = DslProxy.new(context)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Exec the block and return the result
|
25
|
+
proxy._proxy(receiver, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Simple state setup
|
29
|
+
def initialize(context)
|
30
|
+
@_receivers = []
|
31
|
+
@_instance_original_values = {}
|
32
|
+
@_context = context
|
33
|
+
end
|
34
|
+
|
35
|
+
def _proxy(receiver, &block) # :yields: receiver
|
36
|
+
# Sanity!
|
37
|
+
raise 'Cannot proxy with a DslProxy as receiver!' if receiver.respond_to?(:_to_dsl_proxy)
|
38
|
+
|
39
|
+
if @_receivers.empty?
|
40
|
+
# On first proxy call, run each context instance variable,
|
41
|
+
# and set it to ourselves so we can proxy it
|
42
|
+
@_context.instance_variables.each do |var|
|
43
|
+
unless var.to_s.starts_with?('@_')
|
44
|
+
value = @_context.instance_variable_get(var.to_s)
|
45
|
+
@_instance_original_values[var] = value
|
46
|
+
#instance_variable_set(var, value)
|
47
|
+
instance_eval "#{var} = value"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Save the dsl target as our receiver for proxying
|
53
|
+
_push_receiver(receiver)
|
54
|
+
|
55
|
+
# Run the block with ourselves as the new "self", passing the receiver in case
|
56
|
+
# the code wants to disambiguate for some reason
|
57
|
+
result = instance_exec(@_receivers.last, &block)
|
58
|
+
|
59
|
+
# Pop the last receiver off the stack
|
60
|
+
_pop_receiver
|
61
|
+
|
62
|
+
if @_receivers.empty?
|
63
|
+
# Run each local instance variable and re-set it back to the context if it has changed during execution
|
64
|
+
#instance_variables.each do |var|
|
65
|
+
@_context.instance_variables.each do |var|
|
66
|
+
unless var.to_s.starts_with?('@_')
|
67
|
+
value = instance_eval("#{var}")
|
68
|
+
#value = instance_variable_get("#{var}")
|
69
|
+
if @_instance_original_values[var] != value
|
70
|
+
@_context.instance_variable_set(var.to_s, value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
return result
|
77
|
+
end
|
78
|
+
|
79
|
+
# For nesting multiple proxies
|
80
|
+
def _to_dsl_proxy
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Set the currently active receiver
|
85
|
+
def _push_receiver(receiver)
|
86
|
+
@_receivers.push receiver
|
87
|
+
end
|
88
|
+
|
89
|
+
# Remove the currently active receiver, restore old receiver if nested
|
90
|
+
def _pop_receiver
|
91
|
+
@_receivers.pop
|
92
|
+
end
|
93
|
+
|
94
|
+
# Proxies all calls to our receiver, or to the block's context
|
95
|
+
# if the receiver doesn't respond_to? it.
|
96
|
+
def method_missing(method, *args, &block)
|
97
|
+
#$stderr.puts "Method missing: #{method}"
|
98
|
+
if @_receivers.last.respond_to?(method)
|
99
|
+
#$stderr.puts "Proxy [#{method}] to receiver"
|
100
|
+
@_receivers.last.__send__(method, *args, &block)
|
101
|
+
else
|
102
|
+
#$stderr.puts "Proxy [#{method}] to context"
|
103
|
+
@_context.__send__(method, *args, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Let anyone who's interested know what our proxied objects will accept
|
108
|
+
def respond_to?(method, include_private = false)
|
109
|
+
return true if method == :_to_dsl_proxy
|
110
|
+
@_receivers.last.respond_to?(method, include_private) || @_context.respond_to?(method, include_private)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Proxies searching for constants to the context, so that eg Kernel::foo can actually
|
114
|
+
# find Kernel - BasicObject does not partake in the global scope!
|
115
|
+
def self.const_missing(name)
|
116
|
+
#$stderr.puts "Constant missing: #{name} - proxy to context"
|
117
|
+
@_context.class.const_get(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
data/lib/rails-tables/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-tables
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.7.0.rc1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Christopher Keele
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -117,10 +117,12 @@ extra_rdoc_files: []
|
|
117
117
|
files:
|
118
118
|
- app/assets/javascripts/rails-tables.js.coffee
|
119
119
|
- config/routes.rb
|
120
|
+
- lib/rails-tables/datatable/column/renderers.rb
|
120
121
|
- lib/rails-tables/datatable/column.rb
|
121
122
|
- lib/rails-tables/datatable/searching.rb
|
122
123
|
- lib/rails-tables/datatable/sorting.rb
|
123
124
|
- lib/rails-tables/datatable.rb
|
125
|
+
- lib/rails-tables/dsl_proxy.rb
|
124
126
|
- lib/rails-tables/engine.rb
|
125
127
|
- lib/rails-tables/exceptions.rb
|
126
128
|
- lib/rails-tables/model_additions.rb
|
@@ -146,9 +148,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
149
|
none: false
|
148
150
|
requirements:
|
149
|
-
- - ! '
|
151
|
+
- - ! '>'
|
150
152
|
- !ruby/object:Gem::Version
|
151
|
-
version:
|
153
|
+
version: 1.3.1
|
152
154
|
requirements: []
|
153
155
|
rubyforge_project:
|
154
156
|
rubygems_version: 1.8.23
|