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 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.6.6
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) ->
@@ -1,5 +1,6 @@
1
1
  require "rails-tables/engine"
2
2
  require "rails-tables/exceptions"
3
+ require "rails-tables/dsl_proxy"
3
4
  require "rails-tables/datatable"
4
5
  require "rails-tables/model_additions"
5
6
  require "rails-tables/relation_additions"
@@ -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.order
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, *args)
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, args: arguments }
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{ |new_column, index| RailsTables::Column.new(self.class.name, self.model, new_column[:name], index, new_column[:args]) }
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(self.view, object) }
114
+ self.columns.map{ |column| column.render(object) }
110
115
  end
111
116
  end
112
117
 
@@ -1,93 +1,88 @@
1
- class RailsTables::Column
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
- attr_accessor :table_name, :model, :name, :order, :method, :column_source, :render_with, :blank_value, :virtual, :sortable, :searchable
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
- def initialize(table_name, model, name, order, *args)
6
- self.table_name = table_name
7
- self.model = model
8
- self.name = name
9
- self.order = order
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] = '&mdash;' 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
- attributes = args.pop || {}
12
- self.method = attributes.fetch(:method, name).to_s
13
- self.column_source = attributes.fetch(:column_source, '').to_s
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
- virtual = (self.column_source.blank? and not self.model.column_names.include? self.method)
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
- if virtual and not attributes.has_key?(:render_with)
21
- raise Exception,
22
- "Virtual columns are required to supply a render method (render_with: lambda): "\
23
- "Column: #{self.name}, Datatable: #{self.table_name}, Model: #{self.model.name}"
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
- self.render_with = attributes.fetch(:render_with, :default_render)
27
- self.blank_value = attributes.fetch(:blank_value, '&ndash;')
63
+ def default_renderer(object)
64
+ object.send(method_name) if object.respond_to? method_name
65
+ end
28
66
 
29
- define_singleton_method :render do |view, object|
30
- if self.render_with.kind_of? Symbol
31
- content = self.send(self.render_with, view, follow_source(object))
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
- content = self.render_with.call(view, object)
73
+ super
34
74
  end
35
- content.present? ? content.to_s.html_safe : self.blank_value
36
75
  end
37
- end
38
76
 
39
- private
40
-
41
- def follow_source(object)
42
- related = object
43
- self.column_source.split('.').each do |relation|
44
- related = related.try(:send, relation)
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 |method|
13
+ Array(methods).each do |method_name|
14
14
  join column_source
15
- self.searches += [{column_source: column_source.to_s, method: method.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, method: c.method} }
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[:method])) =~ "%#{word}%"
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.method), direction))
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
@@ -1,3 +1,3 @@
1
1
  module RailsTables
2
- VERSION = "0.6.6"
2
+ VERSION = "0.7.0.rc1"
3
3
  end
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.6.6
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: 2012-12-12 00:00:00.000000000 Z
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: '0'
153
+ version: 1.3.1
152
154
  requirements: []
153
155
  rubyforge_project:
154
156
  rubygems_version: 1.8.23