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