tabulatr 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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,