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