tabulatr 0.3.0 → 0.4.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/.gitignore CHANGED
@@ -3,3 +3,6 @@ tabulatr-*.gem
3
3
  Gemfile.lock
4
4
  .bundle
5
5
  spec/dummy_app/tmp/*
6
+ tags
7
+
8
+ .DS_Store
data/Changelog.textile CHANGED
@@ -1,5 +1,25 @@
1
1
  h1. Changelog for tabulatr
2
2
 
3
+ h2. 0.4.0
4
+
5
+ * Mongoid now really works.
6
+ * A lot of cleanup in the finder code (million thanks to <a href="https://github.com/plukevdh">plukevdh</a>)
7
+ * You can use <tt>table_for</tt> for 'normal' arrays that weren't found with <tt>find_for_table</tt>.
8
+ * Added a :name_mapping option to find_for_table to allow filtering by compund expressions, example:
9
+
10
+ <pre>
11
+ @customers = Customer.find_for_table(params,
12
+ name_mapping: {
13
+ name: '("addresses"."firstname" || \' \' || "addresses"."lastname")',
14
+ address: '("addresses"."street" || \' \' || "addresses"."zip_code" || \' \' || "addresses"."city")'
15
+ },
16
+ default_order: 'updated_at desc'
17
+ )
18
+ </pre>
19
+
20
+ * Added Mysql2Adapter to the ActiveRecord connection list (thanks to <a href="https://github.com/AaronLasseigne">AaronLasseigne</a>).
21
+ * Monkeypatch mongoid only if it is present. (thanks to <a href="https://github.com/rwz">rwz</a>)
22
+
3
23
  h2. 0.3.0
4
24
 
5
25
  * Converted into a Rails 3.1 engine and moved css and images to use the new asset pipeline. Works with 3.0.x -- 'opefully. Thanks to all that reported the problem.
@@ -21,15 +21,17 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- # This is somewhat ugly, for it creates the Mongoid module, no matter
25
- # whether it's about to be used. So you will find an empty Mongoid
26
- # module in every Tabulatr project.
27
- module Mongoid
28
- module Document
29
- module ClassMethods
30
- def find_for_table(params, opts={}, &block)
31
- Tabulatr::Finder.find_for_table(self, params, opts, &block)
24
+ # ensure mongoid is loaded *before* monkeypatching
25
+ begin; require 'mongoid'; rescue LoadError; end
26
+
27
+ if defined? ::Mongoid
28
+ module Mongoid
29
+ module Document
30
+ module ClassMethods
31
+ def find_for_table(params, opts={}, &block)
32
+ Tabulatr::Finder.find_for_table(self, params, opts, &block)
33
+ end
32
34
  end
33
35
  end
34
36
  end
35
- end
37
+ end
@@ -59,14 +59,30 @@ class Tabulatr
59
59
  @val = []
60
60
  @record = nil
61
61
  @row_mode = false
62
- @klaz, @classname, @id, @id_type = @records.__classinfo
63
- @pagination = @records.__pagination
64
- @filters = @records.__filters
65
- @sorting = @records.__sorting
66
- @checked = @records.__checked
67
- @store_data = @records.__store_data
68
- @stateful = @records.__stateful
69
- @should_translate = @table_options[:translate]
62
+ if @records.respond_to? :__classinfo
63
+ @klaz, @classname, @id, @id_type = @records.__classinfo
64
+ @pagination = @records.__pagination
65
+ @filters = @records.__filters
66
+ @sorting = @records.__sorting
67
+ @checked = @records.__checked
68
+ @store_data = @records.__store_data
69
+ @stateful = @records.__stateful
70
+ @should_translate = @table_options[:translate]
71
+ else
72
+ @classname, @id, @id_type = nil
73
+ @klaz = @records.first.class
74
+ @pagination = { :page => 1, :pagesize => records.count, :count => records.count, :pages => 1,
75
+ :pagesizes => records.count, :total => records.count }
76
+ @table_options.merge!(
77
+ :paginate => false, # true to show paginator
78
+ :sortable => false, # true to allow sorting (can be specified for every sortable column)
79
+ :selectable => false, # true to render "select all", "select none" and the like
80
+ :info_text => nil,
81
+ :filter => false
82
+ )
83
+ @store_data = []
84
+ @should_translate = @table_options[:translate]
85
+ end
70
86
  end
71
87
 
72
88
  # the actual table definition method. It takes an Array of records, a hash of
@@ -116,7 +132,7 @@ class Tabulatr
116
132
  when :hidden_submit then "IMPLEMENT ME!"
117
133
  when :submit then make_tag(:input, :type => 'submit',
118
134
  :class => @table_options[:submit_class],
119
- :value => t(@table_options[:submit_label]))
135
+ :value => t(@table_options[:submit_label])) if @records.respond_to?(:__classinfo)
120
136
  when :reset then make_tag(:input, :type => 'submit',
121
137
  :class => @table_options[:reset_class],
122
138
  :name => "#{@classname}#{TABLE_FORM_OPTIONS[:reset_state_postfix]}",
@@ -149,6 +165,7 @@ class Tabulatr
149
165
  make_tag(:tbody) do
150
166
  render_table_rows(&block)
151
167
  end # </tbody>
168
+ content_for(@table_options[:footer_content]) if @table_options[:footer_content]
152
169
  end # </table>
153
170
  end
154
171
 
@@ -239,12 +256,12 @@ private
239
256
  end
240
257
  nil
241
258
  end
242
-
259
+
243
260
  def make_image_button(iname, options)
244
261
  inactive = options.delete(:inactive)
245
262
  psrc = @view.image_path File.join(@table_options[:image_path_prefix], iname)
246
263
  if !inactive
247
- make_tag(:input,
264
+ make_tag(:input,
248
265
  options.merge(
249
266
  :type => 'image',
250
267
  :src => psrc
@@ -0,0 +1,55 @@
1
+ class Tabulatr::Adapter
2
+ def initialize(klaz)
3
+ @base = klaz
4
+ @relation = klaz
5
+ end
6
+
7
+ delegate :all, :dup, :count, :limit, to: :@relation
8
+
9
+ def to_sql
10
+ @relation.to_sql if @relation.respond_to? :to_sql
11
+ end
12
+
13
+ def class_to_param
14
+ @relation.to_s.downcase.gsub("/","_")
15
+ end
16
+
17
+ def preconditions_scope(opts)
18
+ opts[:precondition].present? ? @base.where(opts[:precondition]) : @base
19
+ end
20
+
21
+ def order(sortparam, default)
22
+ order_by, order_direction = sort_params(sortparam, default)
23
+ order_by ? { :by => order_by, :direction => order_direction } : nil
24
+ end
25
+
26
+ def sort_params(sortparam, default)
27
+ if sortparam
28
+ if sortparam[:_resort]
29
+ order_by = sortparam[:_resort].first.first
30
+ order_direction = sortparam[:_resort].first.last.first.first
31
+ else
32
+ order_by = sortparam.first.first
33
+ order_direction = sortparam.first.last.first.first
34
+ end
35
+ raise "SECURITY violation, sort field name is '#{n}'" unless /^[\w]+$/.match order_direction
36
+ raise "SECURITY violation, sort field name is '#{n}'" unless /^[\d\w]+$/.match order_by
37
+ else
38
+ if default
39
+ l = default.split(" ")
40
+ raise(":default_order parameter should be of the form 'id asc' or 'name desc'.") if l.length == 0 or l.length > 2
41
+
42
+ order_by = l[0]
43
+ order_direction = l[1] || 'asc'
44
+ else
45
+ order_by = order_direction = nil
46
+ end
47
+ end
48
+
49
+ return order_by, order_direction
50
+ end
51
+ end
52
+
53
+ Dir[File.join(File.dirname(__FILE__), "adapter", "*.rb")].each do |file|
54
+ require file
55
+ end
@@ -0,0 +1,72 @@
1
+ class Tabulatr::Adapter::ActiveRecordAdapter < Tabulatr::Adapter
2
+
3
+ def initialize(klaz)
4
+ set_like_statement unless Tabulatr::SQL_OPTIONS[:like]
5
+
6
+ super klaz
7
+ end
8
+
9
+ def primary_key
10
+ @relation.primary_key.to_sym
11
+ end
12
+
13
+ def key_type
14
+ @relation.columns_hash[primary_key.to_s].type
15
+ end
16
+
17
+ def selected_ids(opts)
18
+ preconditions_scope(opts).select(:id)
19
+ end
20
+
21
+ def table_name
22
+ @relation.table_name
23
+ end
24
+
25
+ def table_name_for_association(assoc)
26
+ @base.reflect_on_association(assoc).table_name
27
+ end
28
+
29
+ def order_for_query(sortparam, default)
30
+ context = order(sortparam, default)
31
+ context.values.join(" ") if context
32
+ end
33
+
34
+ def includes(inc)
35
+ @relation.includes(inc)
36
+ end
37
+
38
+ def includes!(inc)
39
+ @relation = includes(includes)
40
+ end
41
+
42
+ def add_conditions_from(n,v)
43
+ like ||= Tabulatr.sql_options[:like]
44
+ if v.is_a?(String)
45
+ @relation = @relation.where(n => v) unless v.blank?
46
+ elsif v.is_a?(Hash)
47
+ if v[:like].present?
48
+ @relation = @relation.where("#{n} #{like} ?", "%#{v[:like]}%")
49
+ else
50
+ @relation = @relation.where("#{n} >= ?", "#{v[:from]}") if v[:from].present?
51
+ @relation = @relation.where("#{n} <= ?", "#{v[:to]}") if v[:to].present?
52
+ end
53
+ else
54
+ raise "Wrong filter type: #{v.class}"
55
+ end
56
+ end
57
+
58
+ private
59
+ def set_like_statement
60
+ case ActiveRecord::Base.connection.class.to_s
61
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter" then Tabulatr.sql_options(:like => 'LIKE')
62
+ when "ActiveRecord::ConnectionAdapters::Mysql2Adapter" then Tabulatr.sql_options(:like => 'LIKE')
63
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter" then Tabulatr.sql_options(:like => 'ILIKE')
64
+ when "ActiveRecord::ConnectionAdapters::SQLiteAdapter" then Tabulatr.sql_options(:like => 'LIKE')
65
+ when "ActiveRecord::ConnectionAdapters::SQLite3Adapter" then Tabulatr.sql_options(:like => 'LIKE')
66
+ else
67
+ warn("Tabulatr Warning: Don't know which LIKE operator to use for the ConnectionAdapter '#{ActiveRecord::Base.connection.class}'.\n" +
68
+ "Please specify by `Tabulatr.sql_options(:like => '<likeoperator>')`")
69
+ Tabulatr.sql_options(:like => 'LIKE')
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ class Tabulatr::Adapter::MongoidAdapter < Tabulatr::Adapter
2
+ def primary_key
3
+ :id
4
+ end
5
+
6
+ def key_type
7
+ :string
8
+ end
9
+
10
+ def selected_ids(opts)
11
+ preconditions_scope(opts).only(:id)
12
+ end
13
+
14
+ def table_name
15
+ if Object.const_defined?("Mongoid") && @relation.is_a?(Mongoid::Criteria)
16
+ @relation.klass
17
+ else
18
+ @relation
19
+ end.to_s.tableize.gsub('/','_')
20
+ end
21
+
22
+ def table_name_for_association(assoc)
23
+ assoc.to_s.tableize
24
+ end
25
+
26
+ def order_for_query(sortparam, default)
27
+ context = order(sortparam, default)
28
+ context.values.map(&:to_s) if context
29
+ end
30
+
31
+ def includes(includes)
32
+ @relation # do nothing with includes
33
+ end
34
+
35
+ def add_conditions_from(n,v)
36
+ if v.is_a?(String)
37
+ nn = n.split('.').last
38
+ @relation = @relation.where(nn => v) unless v.blank?
39
+ elsif v.is_a?(Hash)
40
+ if v[:like].present?
41
+ nn = n.split('.').last
42
+ @relation = @relation.where(nn => Regexp.new(v[:like]))
43
+ else
44
+ nn = n.split('.').last.to_sym
45
+ @relation = @relation.where(nn.gte => v[:from]) if v[:from].present?
46
+ @relation = @relation.where(nn.lte => v[:to]) if v[:to].present?
47
+ end
48
+ else
49
+ raise "Wrong filter type: #{v.class}"
50
+ end
51
+
52
+ end
53
+ end
54
+
@@ -84,7 +84,7 @@ class Tabulatr
84
84
  if opts[:sort_by]
85
85
  # TODO: SORTING specified by opts[:sort_by]
86
86
  end
87
- concat(if (ass.is_a?(Array) or assoc.collection?) and opts[:map]
87
+ concat(if (ass.is_a?(Array) || ass.respond_to?(:to_ary)) && opts[:map]
88
88
  ass.map do |r|
89
89
  val = h(r.send(opts[:method] || name))
90
90
  if format.is_a?(Proc) then format.call(val)
@@ -34,7 +34,7 @@ module Tabulatr::Finder
34
34
  elsif list.first.is_a?(Fixnum)
35
35
  IdStuffer.stuff(list)
36
36
  else
37
- "GzB" + Base64.encode64s(
37
+ "GzB" + Base64.encode64(
38
38
  Zlib::Deflate.deflate(
39
39
  list.join(Tabulatr.table_form_options[:checked_separator])))
40
40
  end
@@ -65,52 +65,4 @@ module Tabulatr::Finder
65
65
  end
66
66
  end
67
67
 
68
- private
69
-
70
- def self.class_to_param(klaz)
71
- klaz.to_s.downcase.gsub("/","_")
72
- end
73
-
74
- def self.condition_from(rel, typ, n, v)
75
- raise "SECURITY violation, field name is '#{n}'" unless /^[\d\w]+(\.[\d\w]+)?$/.match n
76
- @like ||= Tabulatr.sql_options[:like]
77
- if v.is_a?(String)
78
- if v.present?
79
- if typ == :ar
80
- rel = rel.where(n => v)
81
- elsif typ == :mongoid
82
- nn = n.split('.').last
83
- rel = rel.where(nn => v)
84
- else raise "Unknown db type '#{typ}'"
85
- end
86
- end
87
- elsif v.is_a?(Hash)
88
- if v[:like]
89
- if v[:like].present?
90
- if typ==:ar
91
- rel = rel.where("#{n} #{@like} ?", "%#{v[:like]}%")
92
- elsif typ==:mongoid
93
- nn = n.split('.').last
94
- rel = rel.where(nn => Regexp.new(v[:like]))
95
- else
96
- raise "Unknown db type '#{typ}'"
97
- end
98
- end
99
- else
100
- if typ==:ar
101
- rel = rel.where("#{n} >= ?", "#{v[:from]}") if v[:from].present?
102
- rel = rel.where("#{n} <= ?", "#{v[:to]}") if v[:to].present?
103
- elsif typ==:mongoid
104
- nn = n.split('.').last.to_sym
105
- rel = rel.where(nn.gte => v[:from]) if v[:from].present?
106
- rel = rel.where(nn.lte => v[:to]) if v[:to].present?
107
- else
108
- raise "Unknown db type '#{typ}'"
109
- end
110
- end
111
- else
112
- raise "Wrong filter type: #{v.class}"
113
- end
114
- rel
115
- end
116
68
  end
@@ -23,36 +23,22 @@
23
23
 
24
24
  # These are extensions for use from ActionController instances
25
25
  # In a seperate class call only for clearity
26
+
26
27
  module Tabulatr::Finder
27
28
 
28
29
  # -------------------------------------------------------------------
29
30
  # Called if SomeActveRecordSubclass::find_for_table(params) is called
30
31
  #
31
- def self.find_for_table(klaz, params, o={}, &block)
32
- rel = klaz
33
- typ = if klaz.respond_to?(:descends_from_active_record?) and klaz.descends_from_active_record? then :ar
34
- elsif klaz.include?(Mongoid::Document) then :mongoid
32
+ def self.find_for_table(klaz, params, options={}, &block)
33
+ adapter = if klaz.respond_to?(:descends_from_active_record?) then ::Tabulatr::Adapter::ActiveRecordAdapter.new(klaz)
34
+ elsif klaz.include?(Mongoid::Document) then ::Tabulatr::Adapter::MongoidAdapter.new(klaz)
35
35
  else raise("Don't know how to deal with class '#{klaz}'")
36
36
  end
37
-
38
- # on the first run, get the correct like db-operator, can still be ovrrridden
39
- unless typ != :ar || Tabulatr::SQL_OPTIONS[:like]
40
- case ActiveRecord::Base.connection.class.to_s
41
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter" then Tabulatr.sql_options(:like => 'LIKE')
42
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter" then Tabulatr.sql_options(:like => 'ILIKE')
43
- when "ActiveRecord::ConnectionAdapters::SQLiteAdapter" then Tabulatr.sql_options(:like => 'LIKE')
44
- when "ActiveRecord::ConnectionAdapters::SQLite3Adapter" then Tabulatr.sql_options(:like => 'LIKE')
45
- else
46
- warn("Tabulatr Warning: Don't know which LIKE operator to use for the ConnectionAdapter '#{ActiveRecord::Base.connection.class}'.\n" +
47
- "Please specify by `Tabulatr.sql_options(:like => '<likeoperator>')`")
48
- Tabulatr.sql_options(:like => 'LIKE')
49
- end
50
- end
51
37
 
52
- form_options = Tabulatr.table_form_options
53
- opts = Tabulatr.finder_options.merge(o)
54
- params ||= {} # just to be sure
55
- cname = class_to_param(klaz)
38
+ form_options = Tabulatr.table_form_options
39
+ opts = Tabulatr.finder_options.merge(options)
40
+ params ||= {} # just to be sure
41
+ cname = adapter.class_to_param
56
42
  pagination_name = "#{cname}#{form_options[:pagination_postfix]}"
57
43
  sort_name = "#{cname}#{form_options[:sort_postfix]}"
58
44
  filter_name = "#{cname}#{form_options[:filter_postfix]}"
@@ -62,23 +48,25 @@ module Tabulatr::Finder
62
48
  # before we do anything else, we find whether there's something to do for batch actions
63
49
  checked_param = ActiveSupport::HashWithIndifferentAccess.new({:checked_ids => '', :current_page => []}).
64
50
  merge(params[check_name] || {})
65
- id = (typ==:ar ? klaz.primary_key.to_sym : :id)
66
- id_type = (typ==:ar ? klaz.columns_hash[id.to_s].type : :string)
51
+
52
+ id = adapter.primary_key
53
+ id_type = adapter.key_type
54
+
55
+ # checkboxes
67
56
  checked_ids = uncompress_id_list(checked_param[:checked_ids])
68
57
  new_ids = checked_param[:current_page]
69
58
  new_ids.map!(&:to_i) if id_type==:integer
59
+
70
60
  selected_ids = checked_ids + new_ids
71
61
  batch_param = params[batch_name]
72
62
  if batch_param.present? and block_given?
73
63
  batch_param = batch_param.keys.first.to_sym if batch_param.is_a?(Hash)
74
64
  yield(Invoker.new(batch_param, selected_ids))
75
65
  end
76
-
66
+
77
67
  # then, we obey any "select" buttons if pushed
78
- precon = rel
79
- precon = precon.where(opts[:precondition]) if opts[:precondition].present?
80
68
  if checked_param[:select_all]
81
- selected_ids = (typ==:ar ? precon.select(:id) : precon.only(:id) ).to_a.map { |r| r.send(id) }
69
+ selected_ids = adapter.selected_ids(opts).to_a.map { |r| r.send(id) }
82
70
  elsif checked_param[:select_none]
83
71
  selected_ids = []
84
72
  elsif checked_param[:select_visible]
@@ -88,8 +76,8 @@ module Tabulatr::Finder
88
76
  visible_ids = uncompress_id_list(checked_param[:visible])
89
77
  selected_ids = (selected_ids - visible_ids).sort.uniq
90
78
  end
91
-
92
- # at this point, we've retrieved the filter settings, the sorting setting, the pagination settings and
79
+
80
+ # at this point, we've retrieved the filter settings, the sorting setting, the pagination settings and
93
81
  # the selected_ids.
94
82
  filter_param = (params[filter_name] || {})
95
83
  sortparam = params[sort_name]
@@ -97,12 +85,12 @@ module Tabulatr::Finder
97
85
 
98
86
  # store the state if appropriate
99
87
  if opts[:stateful]
100
- session = opts[:stateful]
88
+ session = options[:stateful]
101
89
  sname = "#{cname}#{form_options[:state_session_postfix]}"
102
90
  raise "give the session as the :stateful parameter in find_for_table, not a '#{session.class}'" \
103
- unless session.is_a?(Rails::version.to_f >= 3.1 ? ActiveSupport::HashWithIndifferentAccess : ActionDispatch::Session::AbstractStore::SessionHash)
91
+ unless session.respond_to? :[]
104
92
  session[sname] ||= {}
105
-
93
+
106
94
  if params["#{cname}#{form_options[:reset_state_postfix]}"]
107
95
  # clicked reset button, reset all and clear session
108
96
  selected_ids = []
@@ -128,59 +116,49 @@ module Tabulatr::Finder
128
116
 
129
117
  # firstly, get the conditions from the filters
130
118
  includes = []
131
- rel = rel.where(opts[:precondition]) if opts[:precondition]
119
+ maps = opts[:name_mapping] || {}
132
120
  conditions = filter_param.each do |t|
133
121
  n, v = t
134
122
  next unless v.present?
135
123
  # FIXME n = name_escaping(n)
136
124
  if (n != form_options[:associations_filter])
137
- table_name = (typ==:ar ? klaz.table_name : klaz.to_s.tableize.gsub('/','_'))
138
- rel = condition_from(rel, typ, "#{table_name}.#{n}", v)
125
+ table_name = adapter.table_name
126
+ nn = if maps[n] then maps[n] else
127
+ t = "#{table_name}.#{n}"
128
+ raise "SECURITY violation, field name is '#{t}'" unless /^[\d\w]+(\.[\d\w]+)?$/.match t
129
+ t
130
+ end
131
+ # puts ">>>>>1>> #{n} -> #{nn}"
132
+ adapter.add_conditions_from(nn, v)
139
133
  else
140
134
  v.each do |t|
141
135
  n,v = t
142
136
  assoc, att = n.split(".").map(&:to_sym)
143
- r = klaz.reflect_on_association(assoc)
144
137
  includes << assoc
145
- table_name = (typ==:ar ? klaz.table_name : klaz.to_s.tableize)
146
- nn = "#{table_name}.#{att}"
147
- rel = condition_from(rel, typ, nn, v)
138
+ table_name = adapter.table_name_for_association(assoc)
139
+ nn = if maps[n] then maps[n] else
140
+ t = "#{table_name}.#{att}"
141
+ raise "SECURITY violation, field name is '#{t}'" unless /^[\d\w]+(\.[\d\w]+)?$/.match t
142
+ t
143
+ end
144
+ # puts ">>>>>2>> #{n} -> #{nn}"
145
+ adapter.add_conditions_from(nn, v)
148
146
  end
149
147
  end
150
148
  end
151
149
 
150
+
152
151
  # more button handling
153
152
  if checked_param[:select_filtered]
154
- all = rel.all
153
+ all = adapter.all
155
154
  selected_ids = (selected_ids + all.map { |r| i=r.send(id); i.is_a?(Fixnum) ? i : i.to_s }).sort.uniq
156
155
  elsif checked_param[:unselect_filtered]
157
- all = rel.dup.all
156
+ all = adapter.dup.all
158
157
  selected_ids = (selected_ids - all.map { |r| i=r.send(id); i.is_a?(Fixnum) ? i : i.to_s }).sort.uniq
159
158
  end
160
159
 
161
160
  # secondly, find the order_by stuff
162
- if sortparam
163
- if sortparam[:_resort]
164
- order_by = sortparam[:_resort].first.first
165
- order_direction = sortparam[:_resort].first.last.first.first
166
- else
167
- order_by = sortparam.first.first
168
- order_direction = sortparam.first.last.first.first
169
- end
170
- raise "SECURITY violation, sort field name is '#{n}'" unless /^[\w]+$/.match order_direction
171
- raise "SECURITY violation, sort field name is '#{n}'" unless /^[\d\w]+$/.match order_by
172
- else
173
- if opts[:default_order]
174
- l = opts[:default_order].split(" ")
175
- raise(":default_order parameter should be of the form 'id asc' or 'name desc'.") \
176
- if l.length == 0 or l.length > 2
177
- order_by = l[0]
178
- order_direction = l[1] || 'asc'
179
- else
180
- order = order_by = order_direction = nil
181
- end
182
- end
183
- order = (typ==:ar ? "#{order_by} #{order_direction}" : [order_by.to_s, order_direction.to_s]) if order_by
161
+ order = adapter.order_for_query(sortparam, opts[:default_order])
184
162
 
185
163
  # thirdly, get the pagination data
186
164
  paginate_options = Tabulatr.paginate_options.merge(opts).merge(pops)
@@ -188,32 +166,33 @@ module Tabulatr::Finder
188
166
  page = paginate_options[:page].to_i
189
167
  page += 1 if paginate_options[:page_right]
190
168
  page -= 1 if paginate_options[:page_left]
191
- c = rel.count
169
+
170
+ c = adapter.includes(includes).count
192
171
  # Group statments return a hash
193
172
  c = c.count unless c.class == Fixnum
173
+
194
174
  pages = (c/pagesize).ceil
195
175
  page = [1, [page, pages].min].max
196
- total = klaz
197
- total = total.where(opts[:precondition]) if opts[:precondition]
176
+
177
+ total = adapter.preconditions_scope(opts).count
198
178
  # here too
199
- total = total.count
200
179
  total = total.count unless total.class == Fixnum
201
-
180
+
181
+
202
182
  # Now, actually find the stuff
203
- found = rel.limit(pagesize.to_i).offset(((page-1)*pagesize).to_i
204
- ).order(order).to_a #, :include => includes
183
+ found = adapter.limit(pagesize.to_i).offset(((page-1)*pagesize).to_i).order(order).to_a
205
184
 
206
185
  # finally, inject methods to retrieve the current 'settings'
207
- found.define_singleton_method(:__filters) do filter_param end
208
- found.define_singleton_method(:__classinfo) do [klaz, cname, id, id_type] end
186
+ found.define_singleton_method(:__filters) { filter_param }
187
+ found.define_singleton_method(:__classinfo) { [klaz, cname, id, id_type] }
209
188
  found.define_singleton_method(:__pagination) do
210
189
  { :page => page, :pagesize => pagesize, :count => c, :pages => pages,
211
190
  :pagesizes => paginate_options[:pagesizes],
212
191
  :total => total }
213
192
  end
214
- found.define_singleton_method(:__sorting) do
215
- order ? { :by => order_by, :direction => order_direction } : nil
216
- end
193
+
194
+ found.define_singleton_method(:__sorting) { adapter.order(sortparam, opts[:default_order]) }
195
+
217
196
  visible_ids = (found.map { |r| r.send(id) })
218
197
  checked_ids = compress_id_list(selected_ids - visible_ids)
219
198
  visible_ids = compress_id_list(visible_ids)
@@ -223,12 +202,9 @@ module Tabulatr::Finder
223
202
  :visible => visible_ids
224
203
  }
225
204
  end
226
- found.define_singleton_method(:__stateful) do
227
- (opts[:stateful] ? true : false)
228
- end
229
- found.define_singleton_method(:__store_data) do
230
- opts[:store_data] || {}
231
- end
205
+
206
+ found.define_singleton_method(:__stateful) { (opts[:stateful] ? true : false) }
207
+ found.define_singleton_method(:__store_data) { opts[:store_data] || {} }
232
208
 
233
209
  found
234
210
  end
@@ -23,6 +23,7 @@
23
23
 
24
24
  module Tabulatr::Formattr
25
25
  ALLOWED_METHODS = [:euro, :dollar, :percent, :lamp]
26
+ #include ActionView::TagHelpers
26
27
 
27
28
  def self.format(nam, val)
28
29
  nam = nam.to_sym
@@ -45,7 +46,10 @@ module Tabulatr::Formattr
45
46
  ("%.2f&thinspace;%%" % 100.0*x).gsub(".", ",")
46
47
  end
47
48
 
48
- def self.lamp(x)
49
-
49
+ def self.lamp(x, mapping)
50
+ s = mapping[x].to_s
51
+ return "?" unless %w{g y r n}.member?(s)
52
+ image_tag("tabulatr/#{s}state.gif").html_safe
50
53
  end
54
+
51
55
  end
@@ -83,6 +83,7 @@ class Tabulatr
83
83
  end
84
84
 
85
85
  def header_action(opts={}, &block)
86
+ opts = normalize_column_options opts
86
87
  make_tag(:th, opts[:th_html]) do
87
88
  concat(t(opts[:header] || ""), :escape_html)
88
89
  end
@@ -98,7 +98,8 @@ class Tabulatr
98
98
  :action => nil, # target action of the wrapping form if applicable
99
99
  :batch_actions => false, # :name => value hash of batch action stuff
100
100
  :translate => false, # call t() for all 'labels' and stuff, possible values are true/:translate or :localize
101
- :row_classes => ['odd', 'even'] # class for the trs
101
+ :row_classes => ['odd', 'even'], # class for the trs
102
+ :footer_content => false # if given, add a <%= content_for <footer_content> %> before the </table>
102
103
  })
103
104
 
104
105
  # these settings are considered constant for the whole application, can not be overridden
@@ -163,7 +164,8 @@ class Tabulatr
163
164
  :default_pagesize => false,
164
165
  :precondition => false,
165
166
  :store_data => false,
166
- :stateful => false
167
+ :stateful => false,
168
+ :name_mapping => nil
167
169
  })
168
170
 
169
171
  # Stupid hack
@@ -1,3 +1,3 @@
1
1
  class Tabulatr
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -9,7 +9,7 @@ if USE_MONGOID
9
9
  field :url, :type => String
10
10
  field :active, :type => Boolean
11
11
  field :description, :type => String
12
- field :price, :type => Fixnum
12
+ field :price, :type => Float
13
13
  field :created_at, :type => Time
14
14
  field :updated_at, :type => Time
15
15
  end
@@ -41,5 +41,5 @@ module DummyApp
41
41
  end
42
42
  end
43
43
 
44
- USE_MONGOID = File.exists?(File.join(Rails::root, 'tmp', 'use_mongoid.txt'))
45
- puts("Using #{USE_MONGOID ? 'Mongoid' : 'ActiveRecord'}")
44
+ USE_MONGOID = ENV['USE_MONGOID']
45
+ puts("Using #{USE_MONGOID ? 'Mongoid' : 'ActiveRecord'}")
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Tabulatrs" do
4
-
4
+
5
5
  Mongoid.master.collections.select do |collection|
6
6
  collection.name !~ /system/
7
7
  end.each(&:drop)
@@ -15,23 +15,6 @@ describe "Tabulatrs" do
15
15
  "occaecat", "cupidatat", "non", "proident", "sunt", "culpa", "qui",
16
16
  "officia", "deserunt", "mollit", "anim", "est", "laborum"]
17
17
 
18
- # control which tests to run. Just to spped testing up
19
- tralse = true
20
- # General stuf
21
- WORKS_IN_GENERAL = CONTAINS_BUTTONS = CONTAINS_COLUMN_HEADERS = CONTAINS_OTHER_CONTROLS = tralse
22
- # This fills in the data, so rather not cmment this out
23
- CONTAINS_ACTUAL_DATA = CONTAINS_ASSOC_DATA = CONTAINS_ACTUAL_DATA_MULTIPLE = CONTAINS_DATA_ON_FURTHER_PAGES = tralse
24
- # Paginatione
25
- PAGES_UP_AND_DOWN = JUMPS_TO_CORRECT_PAGE = CHANGES_PAGE_SIZE = tralse
26
- # Filters
27
- FILTERS = FILTERS_WITH_LIKE = FILTERS_WITH_RANGE = tralse
28
- # Sorting
29
- KNOWS_HOW_TO_SORT = tralse
30
- # Statful
31
- SORTS_STATEFULLY = FILTERS_STATEFULLY = SELECTS_STATEFULLY = tralse
32
- # selecting and batch actions
33
- SELECT_BUTTONS_WORK = KNOWS_HOW_TO_SELECT_AND_APPLY_BATCH_ACTIONS = tralse
34
-
35
18
  vendor1 = Vendor.create!(:name => "ven d'or", :active => true)
36
19
  vendor2 = Vendor.create!(:name => 'producer', :active => true)
37
20
  tag1 = Tag.create!(:title => 'foo')
@@ -43,7 +26,7 @@ describe "Tabulatrs" do
43
26
  it "works in general" do
44
27
  get index_simple_products_path
45
28
  response.status.should be(200)
46
- end if WORKS_IN_GENERAL
29
+ end
47
30
 
48
31
  it "contains buttons" do
49
32
  visit index_simple_products_path
@@ -53,19 +36,19 @@ describe "Tabulatrs" do
53
36
  page.should have_button(Tabulatr::TABLE_OPTIONS[n])
54
37
  end
55
38
  page.should_not have_button(Tabulatr::TABLE_OPTIONS[:reset_label])
56
- end if CONTAINS_BUTTONS
39
+ end
57
40
 
58
41
  it "contains column headers" do
59
42
  visit index_simple_products_path
60
43
  ['Id','Title','Price','Active','Created At','Vendor Created At','Vendor Name','Tags Title','Tags Count'].each do |n|
61
44
  page.should have_content(n)
62
45
  end
63
- end if CONTAINS_COLUMN_HEADERS
46
+ end
64
47
 
65
48
  it "contains other elements" do
66
49
  visit index_simple_products_path
67
50
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 0, 0, 0, 0))
68
- end if CONTAINS_OTHER_CONTROLS
51
+ end
69
52
 
70
53
  it "contains the actual data" do
71
54
  product = Product.create!(:title => names[0], :active => true, :price => 10.0, :description => 'blah blah')
@@ -81,7 +64,7 @@ describe "Tabulatrs" do
81
64
  ids << product.id
82
65
  visit index_simple_products_path
83
66
  page.should have_content("ven d'or")
84
- end if CONTAINS_ACTUAL_DATA
67
+ end
85
68
 
86
69
  it "correctly contains the association data" do
87
70
  product = Product.first
@@ -91,7 +74,7 @@ describe "Tabulatrs" do
91
74
  page.should have_content tag.title
92
75
  page.should have_content(sprintf("--%d--", i+1))
93
76
  end
94
- end if CONTAINS_ASSOC_DATA
77
+ end
95
78
 
96
79
  it "contains the actual data multiple" do
97
80
  9.times do |i|
@@ -103,14 +86,14 @@ describe "Tabulatrs" do
103
86
  page.should have_content((11.0+i).to_s)
104
87
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], i+2, i+2, 0, i+2))
105
88
  end
106
- end if CONTAINS_ACTUAL_DATA_MULTIPLE
89
+ end
107
90
 
108
91
  it "contains row identifiers" do
109
92
  visit index_simple_products_path
110
93
  Product.all.each do |product|
111
94
  page.should have_css("#product_#{product.id}")
112
95
  end
113
- end if CONTAINS_ACTUAL_DATA
96
+ end
114
97
 
115
98
  it "contains the further data on the further pages" do
116
99
  names[10..-1].each_with_index do |n,i|
@@ -122,7 +105,7 @@ describe "Tabulatrs" do
122
105
  page.should_not have_content((30.0+i).to_s)
123
106
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, i+11, 0, i+11))
124
107
  end
125
- end if CONTAINS_DATA_ON_FURTHER_PAGES
108
+ end
126
109
  end
127
110
 
128
111
  describe "Pagination" do
@@ -163,7 +146,7 @@ describe "Tabulatrs" do
163
146
  click_button('product_pagination_page_left')
164
147
  end
165
148
  end
166
- end if PAGES_UP_AND_DOWN
149
+ end
167
150
 
168
151
  it "jumps to the correct page" do
169
152
  visit index_simple_products_path
@@ -187,7 +170,7 @@ describe "Tabulatrs" do
187
170
  page.should have_button('product_pagination_page_right')
188
171
  end
189
172
  end
190
- end if JUMPS_TO_CORRECT_PAGE
173
+ end
191
174
 
192
175
  it "changes the page size" do
193
176
  visit index_simple_products_path
@@ -199,7 +182,7 @@ describe "Tabulatrs" do
199
182
  end
200
183
  page.should_not have_content(names[s])
201
184
  end
202
- end if CHANGES_PAGE_SIZE
185
+ end
203
186
  end
204
187
 
205
188
  describe "Filters" do
@@ -214,7 +197,7 @@ describe "Tabulatrs" do
214
197
  click_button("Apply")
215
198
  page.should_not have_content("lorem")
216
199
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 0, names.length, 0, 0))
217
- end if FILTERS
200
+ end
218
201
 
219
202
  it "filters with like" do
220
203
  visit index_filters_products_path
@@ -226,7 +209,7 @@ describe "Tabulatrs" do
226
209
  #save_and_open_page
227
210
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], [10,tot].min, names.length, 0, tot))
228
211
  end
229
- end if FILTERS_WITH_LIKE
212
+ end
230
213
 
231
214
  it "filters with range" do
232
215
  visit index_filters_products_path
@@ -250,7 +233,7 @@ describe "Tabulatrs" do
250
233
  tot = n-i*2
251
234
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], [10,tot].min, n, 0, tot))
252
235
  end
253
- end if FILTERS_WITH_RANGE
236
+ end
254
237
  end
255
238
 
256
239
  describe "Sorting" do
@@ -269,11 +252,12 @@ describe "Tabulatrs" do
269
252
  (1..10).each do |i|
270
253
  page.should have_content snames[i-1]
271
254
  end
272
- end if KNOWS_HOW_TO_SORT
255
+ end
273
256
  end
274
257
 
275
258
  describe "statefulness" do
276
259
  it "sorts statefully" do
260
+ Capybara.reset_sessions!
277
261
  visit index_stateful_products_path
278
262
  click_button("product_sort_title_desc")
279
263
  snames = names.sort
@@ -288,7 +272,7 @@ describe "Tabulatrs" do
288
272
  (1..10).each do |i|
289
273
  page.should have_content names[i-1]
290
274
  end
291
- end if SORTS_STATEFULLY
275
+ end
292
276
 
293
277
  it "filters statefully" do
294
278
  Capybara.reset_sessions!
@@ -300,9 +284,10 @@ describe "Tabulatrs" do
300
284
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 1, names.length, 0, 1))
301
285
  click_button("Reset")
302
286
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, names.length, 0, names.length))
303
- end if FILTERS_STATEFULLY
287
+ end
304
288
 
305
289
  it "selects statefully" do
290
+ Capybara.reset_sessions!
306
291
  visit index_stateful_products_path
307
292
  fill_in("product_filter[title]", :with => "")
308
293
  click_button("Apply")
@@ -321,7 +306,7 @@ describe "Tabulatrs" do
321
306
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], names.length % 10, n, tot, n))
322
307
  click_button("Reset")
323
308
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, names.length, 0, names.length))
324
- end if SELECTS_STATEFULLY
309
+ end
325
310
 
326
311
  end
327
312
 
@@ -363,7 +348,7 @@ describe "Tabulatrs" do
363
348
  fill_in("product_filter[title][like]", :with => "")
364
349
  click_button("Apply")
365
350
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, n, n-tot, n))
366
- end if SELECT_BUTTONS_WORK
351
+ end
367
352
 
368
353
  it "knows how to select and apply batch actions" do
369
354
  visit index_select_products_path
@@ -382,7 +367,7 @@ describe "Tabulatrs" do
382
367
  tot = n-3*(n/10)
383
368
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, tot, 0, tot))
384
369
  #save_and_open_page
385
- end if KNOWS_HOW_TO_SELECT_AND_APPLY_BATCH_ACTIONS
370
+ end
386
371
  end
387
372
 
388
373
  # describe "GET /products empty" do
data/tabulatr.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.rdoc_options = ['--charset=UTF-8']
19
-
19
+
20
20
 
21
21
  s.add_runtime_dependency('rails', '>= 3.0.0')
22
22
  s.add_dependency('whiny_hash', '>= 0.0.2')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabulatr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-08-20 00:00:00.000000000Z
13
+ date: 2012-01-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
17
- requirement: &70348423259000 !ruby/object:Gem::Requirement
17
+ requirement: &70187434600440 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 3.0.0
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70348423259000
25
+ version_requirements: *70187434600440
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: whiny_hash
28
- requirement: &70348423258260 !ruby/object:Gem::Requirement
28
+ requirement: &70187434599820 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 0.0.2
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70348423258260
36
+ version_requirements: *70187434599820
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: id_stuffer
39
- requirement: &70348423257540 !ruby/object:Gem::Requirement
39
+ requirement: &70187434599320 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: 0.0.1
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *70348423257540
47
+ version_requirements: *70187434599320
48
48
  description: A tight DSL to build tables of ActiveRecord or Mongoid models with sorting,
49
49
  pagination, finding/filtering, selecting and batch actions. Tries to do for tables
50
50
  what formtastic and simple_form did for forms.
@@ -92,6 +92,9 @@ files:
92
92
  - lib/tabulatr.rb
93
93
  - lib/tabulatr/engine.rb
94
94
  - lib/tabulatr/tabulatr.rb
95
+ - lib/tabulatr/tabulatr/adapter.rb
96
+ - lib/tabulatr/tabulatr/adapter/active_record.rb
97
+ - lib/tabulatr/tabulatr/adapter/mongoid.rb
95
98
  - lib/tabulatr/tabulatr/batch_actions.rb
96
99
  - lib/tabulatr/tabulatr/check_controls.rb
97
100
  - lib/tabulatr/tabulatr/data_cell.rb
@@ -200,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
203
  version: '0'
201
204
  requirements: []
202
205
  rubyforge_project:
203
- rubygems_version: 1.8.6
206
+ rubygems_version: 1.8.10
204
207
  signing_key:
205
208
  specification_version: 3
206
209
  summary: A tight DSL to build tables of ActiveRecord or Mongoid models with sorting,