tabulatr 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@ pkg
2
2
  tabulatr-*.gem
3
3
  Gemfile.lock
4
4
  .bundle
5
+ spec/dummy_app/tmp/*
data/Changelog.textile CHANGED
@@ -1,5 +1,12 @@
1
1
  h1. Changelog for tabulatr
2
2
 
3
+ h2. 0.2.0
4
+
5
+ * Mongoid can also be used in place of ActiveRecord
6
+ * fixed handling of the primary_key column and name
7
+ * Added dom_id to tables tr. Thanks to <a href="https://github.com/sterrym">sterrym (Tim Glen)</a>!
8
+ * Added note on "Request-URI Too Large error" to README
9
+
3
10
  h2. 0.1.3
4
11
 
5
12
  * nicer table headers. Thanks to <a href="https://github.com/troya2" target="_blank">troya2 (Troy Anderson)</a>!
data/Gemfile CHANGED
@@ -14,4 +14,5 @@ group :development, :test do
14
14
  gem 'capybara'
15
15
  gem 'launchy'
16
16
  gem 'database_cleaner'
17
+ gem 'mongoid'
17
18
  end
data/README.textile CHANGED
@@ -4,7 +4,7 @@ h1. Tabulatr - Index Tables made easy, finally
4
4
 
5
5
  h2. Mission Objective
6
6
 
7
- Tabulatr aims at making the ever-recurring task of creating listings of ActiveRecord models simple and uniform.
7
+ Tabulatr aims at making the ever-recurring task of creating listings of ActiveRecord or Mongoid models simple and uniform.
8
8
 
9
9
  We found ourselves reinventing the wheel in every project we made, by using
10
10
 
@@ -313,16 +313,22 @@ To override these, you should, in an initializer, call
313
313
  h2. Dependencies
314
314
 
315
315
  We use
316
- * <a href="http://github.com/provideal/whiny_hash">WhinyHash</a> to handle the options in a fail-early-manner,
317
- * <a href="http://github.com/provideal/id_stuffer">IdStuffer</a> to compress the _remembered_ ids.
316
+ * <a href="http://github.com/provideal/whiny_hash">whiny_hash</a> to handle the options in a fail-early-manner,
317
+ * <a href="http://github.com/provideal/id_stuffer">id_stuffer</a> to compress the _remembered_ ids.
318
318
  * And... eh... It's an extension for Rails 3, so it might be handy to have a version of Rails 3 and Ruby 1.8.? or 1.9.?.
319
319
 
320
320
  h2. Bugs
321
321
 
322
- There are, definitely, roughly 999 bugs in Tabulatr, although we do some testing (see <tt>spec/</tt>). So if you hunt them, please let me know using the <a href="https://github.com/provideal/tabulatr/issues">GitHub Bugtracker</a>.
322
+ h3. Request-URI Too Large error
323
+
324
+ Since everyhing about the state of the table is intentionally encoded in the URI, the URI may become somewhat bloated if fancy selecting is used. By using <a href="http://github.com/provideal/id_stuffer">id_stuffer</a>, we drastically reduced the size of the URI (that's the reason we actually made it), but it may still become rather large. This is a problem particulary when using WEBrick, because WEBricks URIs must not exceed 2048 characters. And this limit is hard-coded IIRC. So – If you run into this limitation – please consider using another server. (Thanks to <a href="https://github.com/stepheneb">stepheneb</a> for calling my attention back to this.)
325
+
326
+ h3. Other, new bugs
327
+
328
+ There are roughly another 997 bugs in Tabulatr, although we do some testing (see <tt>spec/</tt>). So if you hunt them, please let me know using the <a href="https://github.com/provideal/tabulatr/issues">GitHub Bugtracker</a>.
323
329
 
324
330
  h2. Contributing to tabulatr
325
-
331
+
326
332
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
327
333
  * Check out the <a href="https://github.com/provideal/tabulatr/issues">issue tracker</a> to make sure someone already hasn't requested it and/or contributed it
328
334
  * Fork the project
@@ -26,7 +26,7 @@
26
26
  if Object.const_defined? "ActiveRecord"
27
27
  class ActiveRecord::Base
28
28
  def self.find_for_table(params, opts={}, &block)
29
- Tabulatr::Finder.find_for_active_record_table(self, params, opts, &block)
29
+ Tabulatr::Finder.find_for_table(self, params, opts, &block)
30
30
  end
31
31
  end
32
32
  end
@@ -21,13 +21,14 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- # buggy like hell
25
- if Object.const_defined? "Mongoid"
26
- raise "Mongoid support is buggy like hell as of now"
27
- module Mongoid::Document
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
28
29
  module ClassMethods
29
30
  def find_for_table(params, opts={}, &block)
30
- Tabulatr.find_for_mongoid_table(self, params, opts, &block)
31
+ Tabulatr::Finder.find_for_table(self, params, opts, &block)
31
32
  end
32
33
  end
33
34
  end
@@ -80,8 +80,12 @@ class Tabulatr
80
80
  assoc = @record.class.reflect_on_association(relation)
81
81
  make_tag(:td, opts[:td_html]) do
82
82
  format = opts[:format]
83
- concat(if assoc.collection? and opts[:map]
84
- @record.send(relation.to_sym).map do |r|
83
+ ass = @record.send(relation.to_sym)
84
+ if opts[:sort_by]
85
+ # TODO: SORTING specified by opts[:sort_by]
86
+ end
87
+ concat(if (ass.is_a?(Array) or assoc.collection?) and opts[:map]
88
+ ass.map do |r|
85
89
  val = h(r.send(opts[:method] || name))
86
90
  if format.is_a?(Proc) then format.call(val)
87
91
  elsif format.is_a?(String) then h(format % val)
@@ -90,8 +94,8 @@ class Tabulatr
90
94
  end
91
95
  end.join(opts[:join_symbol])
92
96
  else
93
- ass = @record.send(relation.to_sym)
94
97
  return '' unless ass
98
+ #puts ass.to_s
95
99
  val = h(ass.send(opts[:method] || name))
96
100
  val = if format.is_a?(Proc) then format.call(val)
97
101
  elsif format.is_a?(String) then h(format % val)
@@ -106,10 +110,10 @@ class Tabulatr
106
110
  raise "Whatever that's for!" if block_given?
107
111
  iname = "#{@classname}#{@table_form_options[:checked_postfix]}[current_page][]"
108
112
  make_tag(:td, opts[:td_html]) do
109
- checked = @checked[:selected].member?(@record.id.to_s) ? :checked : nil
113
+ checked = @checked[:selected].member?(@record.send(@id)) ? :checked : nil
110
114
  make_tag(:input, :type => 'checkbox', :name => iname,
111
- :id => "#{@classname}#{@table_form_options[:checked_postfix]}_#{@record.id.to_s}",
112
- :value => @record.id, :checked => checked)
115
+ :id => "#{@classname}#{@table_form_options[:checked_postfix]}_#{@record.send(@id).to_s}",
116
+ :value => @record.send(@id), :checked => checked)
113
117
  end
114
118
  end
115
119
 
@@ -28,9 +28,15 @@ module Tabulatr::Finder
28
28
  # -------------------------------------------------------------------
29
29
  # Called if SomeActveRecordSubclass::find_for_table(params) is called
30
30
  #
31
- def self.find_for_active_record_table(klaz, params, o={}, &block)
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
35
+ else raise("Don't know how to deal with class '#{klaz}'")
36
+ end
37
+
32
38
  # on the first run, get the correct like db-operator, can still be ovrrridden
33
- unless Tabulatr::SQL_OPTIONS[:like]
39
+ unless typ != :ar || Tabulatr::SQL_OPTIONS[:like]
34
40
  case ActiveRecord::Base.connection.class.to_s
35
41
  when "ActiveRecord::ConnectionAdapters::MysqlAdapter" then Tabulatr.sql_options(:like => 'LIKE')
36
42
  when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter" then Tabulatr.sql_options(:like => 'ILIKE')
@@ -56,8 +62,11 @@ module Tabulatr::Finder
56
62
  # before we do anything else, we find whether there's something to do for batch actions
57
63
  checked_param = ActiveSupport::HashWithIndifferentAccess.new({:checked_ids => '', :current_page => []}).
58
64
  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)
59
67
  checked_ids = uncompress_id_list(checked_param[:checked_ids])
60
68
  new_ids = checked_param[:current_page]
69
+ new_ids.map!(&:to_i) if id_type==:integer
61
70
  selected_ids = checked_ids + new_ids
62
71
  batch_param = params[batch_name]
63
72
  if batch_param.present? and block_given?
@@ -66,10 +75,10 @@ module Tabulatr::Finder
66
75
  end
67
76
 
68
77
  # then, we obey any "select" buttons if pushed
69
- precondition = opts[:precondition] || "(1=1)"
78
+ precon = rel
79
+ precon = precon.where(opts[:precondition]) if opts[:precondition].present?
70
80
  if checked_param[:select_all]
71
- all = klaz.find :all, :conditions => precondition, :select => :id
72
- selected_ids = all.map { |r| r.id.to_s }
81
+ selected_ids = (typ==:ar ? precon.select(:id) : precon.only(:id) ).to_a.map { |r| r.send(id) }
73
82
  elsif checked_param[:select_none]
74
83
  selected_ids = []
75
84
  elsif checked_param[:select_visible]
@@ -118,33 +127,34 @@ module Tabulatr::Finder
118
127
 
119
128
  # firstly, get the conditions from the filters
120
129
  includes = []
121
- precondition = opts[:precondition] || "(1=1)"
122
- conditions = filter_param.inject([precondition.dup, []]) do |c, t|
130
+ rel = rel.where(opts[:precondition]) if opts[:precondition]
131
+ conditions = filter_param.each do |t|
123
132
  n, v = t
133
+ next unless v.present?
124
134
  # FIXME n = name_escaping(n)
125
135
  if (n != form_options[:associations_filter])
126
- condition_from("#{klaz.table_name}.#{n}",v,c)
136
+ table_name = (typ==:ar ? klaz.table_name : klaz.to_s.tableize.gsub('/','_'))
137
+ rel = condition_from(rel, typ, "#{table_name}.#{n}", v)
127
138
  else
128
- v.inject(c) do |c,t|
139
+ v.each do |t|
129
140
  n,v = t
130
141
  assoc, att = n.split(".").map(&:to_sym)
131
142
  r = klaz.reflect_on_association(assoc)
132
143
  includes << assoc
133
- tname = r.table_name
134
- nn = "#{tname}.#{att}"
135
- condition_from(nn,v,c)
144
+ table_name = (typ==:ar ? klaz.table_name : klaz.to_s.tableize)
145
+ nn = "#{table_name}.#{att}"
146
+ rel = condition_from(rel, typ, nn, v)
136
147
  end
137
148
  end
138
149
  end
139
- conditions = [conditions.first] + conditions.last
140
150
 
141
151
  # more button handling
142
152
  if checked_param[:select_filtered]
143
- all = klaz.find :all, :conditions => conditions, :select => :id, :include => includes
144
- selected_ids = (selected_ids + all.map { |r| r.id.to_s }).sort.uniq
153
+ all = rel.all
154
+ selected_ids = (selected_ids + all.map { |r| i=r.send(id); i.is_a?(Fixnum) ? i : i.to_s }).sort.uniq
145
155
  elsif checked_param[:unselect_filtered]
146
- all = klaz.find :all, :conditions => conditions, :select => :id, :include => includes
147
- selected_ids = (selected_ids - all.map { |r| r.id.to_s }).sort.uniq
156
+ all = rel.dup.all
157
+ selected_ids = (selected_ids - all.map { |r| i=r.send(id); i.is_a?(Fixnum) ? i : i.to_s }).sort.uniq
148
158
  end
149
159
 
150
160
  # secondly, find the order_by stuff
@@ -158,7 +168,6 @@ module Tabulatr::Finder
158
168
  end
159
169
  raise "SECURITY violation, sort field name is '#{n}'" unless /^[\w]+$/.match order_direction
160
170
  raise "SECURITY violation, sort field name is '#{n}'" unless /^[\d\w]+$/.match order_by
161
- order = "#{order_by} #{order_direction}"
162
171
  else
163
172
  if opts[:default_order]
164
173
  l = opts[:default_order].split(" ")
@@ -166,11 +175,11 @@ module Tabulatr::Finder
166
175
  if l.length == 0 or l.length > 2
167
176
  order_by = l[0]
168
177
  order_direction = l[1] || 'asc'
169
- order = "#{order_by} #{order_direction}"
170
178
  else
171
179
  order = order_by = order_direction = nil
172
180
  end
173
181
  end
182
+ order = (typ==:ar ? "#{order_by} #{order_direction}" : [order_by.to_s, order_direction.to_s]) if order_by
174
183
 
175
184
  # thirdly, get the pagination data
176
185
  paginate_options = Tabulatr.paginate_options.merge(opts).merge(pops)
@@ -178,40 +187,41 @@ module Tabulatr::Finder
178
187
  page = paginate_options[:page].to_i
179
188
  page += 1 if paginate_options[:page_right]
180
189
  page -= 1 if paginate_options[:page_left]
181
- c = klaz.count :conditions => conditions, :include => includes
190
+ c = rel.count
182
191
  pages = (c/pagesize).ceil
183
192
  page = [1, [page, pages].min].max
184
-
193
+ total = klaz
194
+ total = total.where(opts[:precondition]) if opts[:precondition]
195
+ total = total.count
196
+
185
197
  # Now, actually find the stuff
186
- found = klaz.find :all, :conditions => conditions,
187
- :limit => pagesize.to_i, :offset => ((page-1)*pagesize).to_i,
188
- :order => order, :include => includes
198
+ found = rel.limit(pagesize.to_i).offset(((page-1)*pagesize).to_i)
199
+ .order(order).to_a #, :include => includes
189
200
 
190
201
  # finally, inject methods to retrieve the current 'settings'
191
- fio = Tabulatr.finder_inject_options
192
- found.define_singleton_method(fio[:filters]) do filter_param end
193
- found.define_singleton_method(fio[:classname]) do cname end
194
- found.define_singleton_method(fio[:pagination]) do
202
+ found.define_singleton_method(:__filters) do filter_param end
203
+ found.define_singleton_method(:__classinfo) do [cname, id, id_type] end
204
+ found.define_singleton_method(:__pagination) do
195
205
  { :page => page, :pagesize => pagesize, :count => c, :pages => pages,
196
206
  :pagesizes => paginate_options[:pagesizes],
197
- :total => klaz.count(:conditions => precondition) }
207
+ :total => total }
198
208
  end
199
- found.define_singleton_method(fio[:sorting]) do
209
+ found.define_singleton_method(:__sorting) do
200
210
  order ? { :by => order_by, :direction => order_direction } : nil
201
211
  end
202
- visible_ids = (found.map { |r| r.id.to_s })
212
+ visible_ids = (found.map { |r| r.send(id) })
203
213
  checked_ids = compress_id_list(selected_ids - visible_ids)
204
214
  visible_ids = compress_id_list(visible_ids)
205
- found.define_singleton_method(fio[:checked]) do
215
+ found.define_singleton_method(:__checked) do
206
216
  { :selected => selected_ids,
207
217
  :checked_ids => checked_ids,
208
218
  :visible => visible_ids
209
219
  }
210
220
  end
211
- found.define_singleton_method(fio[:stateful]) do
221
+ found.define_singleton_method(:__stateful) do
212
222
  (opts[:stateful] ? true : false)
213
223
  end
214
- found.define_singleton_method(fio[:store_data]) do
224
+ found.define_singleton_method(:__store_data) do
215
225
  opts[:store_data] || {}
216
226
  end
217
227
 
@@ -24,18 +24,32 @@
24
24
  # These are extensions for use from ActionController instances
25
25
  module Tabulatr::Finder
26
26
 
27
- require File.join(File.dirname(__FILE__), 'finder', 'find_for_active_record_table')
28
- require File.join(File.dirname(__FILE__), 'finder', 'find_for_mongoid_table')
27
+ require File.join(File.dirname(__FILE__), 'finder', 'find_for_table')
29
28
 
30
29
  # compress the list of ids as good as I could imagine ;)
31
30
  # uses fancy base twisting
32
31
  def self.compress_id_list(list)
33
- IdStuffer.stuff(list)
32
+ if list.length == 0
33
+ ""
34
+ elsif list.first.is_a?(Fixnum)
35
+ IdStuffer.stuff(list)
36
+ else
37
+ "GzB" + Base64.encode64s(
38
+ Zlib::Deflate.deflate(
39
+ list.join(Tabulatr.table_form_options[:checked_separator])))
40
+ end
34
41
  end
35
42
 
36
43
  # inverse of compress_id_list
37
44
  def self.uncompress_id_list(str)
38
- IdStuffer.unstuff(str).map &:to_s
45
+ if !str.present?
46
+ []
47
+ elsif str.starts_with?("GzB")
48
+ Zlib::Inflate.inflate(Base64.decode64(str[3..-1]))
49
+ .split(Tabulatr.table_form_options[:checked_separator])
50
+ else
51
+ IdStuffer.unstuff(str)
52
+ end
39
53
  end
40
54
 
41
55
  class Invoker
@@ -57,26 +71,46 @@ private
57
71
  klaz.to_s.downcase.gsub("/","_")
58
72
  end
59
73
 
60
- def self.condition_from(n,v,c)
74
+ def self.condition_from(rel, typ, n, v)
61
75
  raise "SECURITY violation, field name is '#{n}'" unless /^[\d\w]+(\.[\d\w]+)?$/.match n
62
76
  @like ||= Tabulatr.sql_options[:like]
63
- nc = c
64
77
  if v.is_a?(String)
65
78
  if v.present?
66
- nc = [c[0] << " AND (#{n} = ?) ", c[1] << v]
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
67
86
  end
68
87
  elsif v.is_a?(Hash)
69
88
  if v[:like]
70
89
  if v[:like].present?
71
- nc = [c[0] << " AND (#{n} #{@like} ?) ", c[1] << "%#{v[:like]}%"]
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
72
98
  end
73
99
  else
74
- nc = [c[0] << " AND (#{n} >= ?) ", c[1] << "#{v[:from]}"] if v[:from].present?
75
- nc = [nc[0] << " AND (#{n} <= ?) ", nc[1] << "#{v[:to]}"] if v[:to].present?
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
76
110
  end
77
111
  else
78
112
  raise "Wrong filter type: #{v.class}"
79
113
  end
80
- nc
114
+ rel
81
115
  end
82
116
  end
@@ -27,7 +27,7 @@ class Tabulatr
27
27
  def render_paginator
28
28
  # get the current pagination state
29
29
  pagination_name = "#{@classname}#{TABLE_FORM_OPTIONS[:pagination_postfix]}"
30
- pparams = @records.send(FINDER_INJECT_OPTIONS[:pagination])
30
+ pparams = @records.__pagination
31
31
  page = pparams[:page].to_i
32
32
  pages = pparams[:pages].to_i
33
33
  pagesize = pparams[:pagesize].to_i
@@ -153,22 +153,10 @@ class Tabulatr
153
153
  :link => false, # proc or symbol to make the content a link
154
154
  :join_symbol => ', ', # symbol used to join the elements of 'many' associations
155
155
  :map => true, # whether to map the call on individual records (true) or call on the list (false)
156
+ :sort_by => false, # sort the elements of an association
156
157
  :sortable => true # if set, sorting-stuff is added to the header cell
157
158
  })
158
159
 
159
- # these settings are considered constant for the whole application, can not be overridden
160
- # on a per-table basis.
161
- # That's necessary to allow find_for_table to work properly
162
- FINDER_INJECT_OPTIONS = WhinyHash.new({
163
- :pagination => :__pagination,
164
- :filters => :__filters,
165
- :classname => :__classname,
166
- :sorting => :__sorting,
167
- :checked => :__checked,
168
- :store_data => :__store_data,
169
- :stateful => :__stateful
170
- })
171
-
172
160
  # defaults for the find_for_table
173
161
  FINDER_OPTIONS = WhinyHash.new({
174
162
  :default_order => false,
@@ -183,11 +171,6 @@ class Tabulatr
183
171
  :like => nil
184
172
  })
185
173
 
186
- def self.finder_inject_options(n=nil)
187
- FINDER_INJECT_OPTIONS.merge!(n) if n
188
- FINDER_INJECT_OPTIONS
189
- end
190
-
191
174
  def self.finder_options(n=nil)
192
175
  FINDER_OPTIONS.merge!(n) if n
193
176
  FINDER_OPTIONS
@@ -41,6 +41,7 @@ class Tabulatr
41
41
  include ActionView::Helpers::FormTagHelper
42
42
  include ActionView::Helpers::FormOptionsHelper
43
43
  include ActionView::Helpers::TranslationHelper
44
+ include ActionView::Helpers::RecordTagHelper
44
45
 
45
46
  # Constructor of Tabulatr
46
47
  #
@@ -56,13 +57,13 @@ class Tabulatr
56
57
  @val = []
57
58
  @record = nil
58
59
  @row_mode = false
59
- @classname = @records.send(FINDER_INJECT_OPTIONS[:classname])
60
- @pagination = @records.send(FINDER_INJECT_OPTIONS[:pagination])
61
- @filters = @records.send(FINDER_INJECT_OPTIONS[:filters])
62
- @sorting = @records.send(FINDER_INJECT_OPTIONS[:sorting])
63
- @checked = @records.send(FINDER_INJECT_OPTIONS[:checked])
64
- @store_data = @records.send(FINDER_INJECT_OPTIONS[:store_data])
65
- @stateful = @records.send(FINDER_INJECT_OPTIONS[:stateful])
60
+ @classname, @id, @id_type = @records.__classinfo
61
+ @pagination = @records.__pagination
62
+ @filters = @records.__filters
63
+ @sorting = @records.__sorting
64
+ @checked = @records.__checked
65
+ @store_data = @records.__store_data
66
+ @stateful = @records.__stateful
66
67
  @should_translate = @table_options[:translate]
67
68
  end
68
69
 
@@ -214,7 +215,7 @@ private
214
215
  else
215
216
  rc = nil
216
217
  end
217
- make_tag(:tr, row_html.merge(:class => rc)) do
218
+ make_tag(:tr, row_html.merge(:class => rc, :id => dom_id(record))) do
218
219
  yield(data_row_builder(record))
219
220
  end # </tr>
220
221
  end
@@ -1,3 +1,3 @@
1
1
  class Tabulatr
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,6 +1,23 @@
1
- class Product < ActiveRecord::Base
1
+ if USE_MONGOID
2
+ class Product
3
+ include Mongoid::Document
2
4
 
3
- has_and_belongs_to_many :tags
4
- belongs_to :vendor
5
-
6
- end
5
+ has_and_belongs_to_many :tags
6
+ belongs_to :vendor
7
+
8
+ field :name, :type => String
9
+ field :url, :type => String
10
+ field :active, :type => Boolean
11
+ field :description, :type => String
12
+ field :price, :type => Fixnum
13
+ field :created_at, :type => Time
14
+ field :updated_at, :type => Time
15
+ end
16
+ else
17
+ class Product < ActiveRecord::Base
18
+
19
+ has_and_belongs_to_many :tags
20
+ belongs_to :vendor
21
+
22
+ end
23
+ end
@@ -1,3 +1,13 @@
1
- class Tag < ActiveRecord::Base
2
- has_and_belongs_to_many :products
3
- end
1
+ if USE_MONGOID
2
+ class Tag
3
+ include Mongoid::Document
4
+ has_and_belongs_to_many :products
5
+ field :title, :type => String
6
+ field :created_at, :type => Time
7
+ field :updated_at, :type => Time
8
+ end
9
+ else
10
+ class Tag < ActiveRecord::Base
11
+ has_and_belongs_to_many :products
12
+ end
13
+ end
@@ -1,5 +1,17 @@
1
- class Vendor < ActiveRecord::Base
2
-
3
- has_many :products
4
-
1
+ if USE_MONGOID
2
+ class Vendor
3
+ include Mongoid::Document
4
+
5
+ has_many :products
6
+ field :name, :type => String
7
+ field :url, :type => String
8
+ field :active, :type => Boolean
9
+ field :description, :type => String
10
+ field :created_at, :type => Time
11
+ field :updated_at, :type => Time
12
+ end
13
+ else
14
+ class Vendor < ActiveRecord::Base
15
+ has_many :products
16
+ end
5
17
  end
@@ -4,6 +4,7 @@
4
4
  t.column :price
5
5
  t.column :active
6
6
  t.column :created_at
7
+ t.association :vendor, :created_at
7
8
  t.association :vendor, :name
8
9
  t.association :tags, :title
9
10
  t.association :tags, :count, :map => false, :format => '--%d--'
@@ -40,3 +40,6 @@ module DummyApp
40
40
  config.filter_parameters += [:password]
41
41
  end
42
42
  end
43
+
44
+ USE_MONGOID = File.exists?(File.join(Rails::root, 'tmp', 'use_mongoid.txt'))
45
+ puts("Using #{USE_MONGOID ? 'Mongoid' : 'ActiveRecord'}")
@@ -0,0 +1,17 @@
1
+ defaults: &defaults
2
+ host: localhost
3
+ # slaves:
4
+ # - host: slave1.local
5
+ # port: 27018
6
+ # - host: slave2.local
7
+ # port: 27019
8
+ persist_in_safe_mode: true
9
+
10
+ development:
11
+ <<: *defaults
12
+ database: tabulatr_development
13
+
14
+ test:
15
+ <<: *defaults
16
+ database: tabulatr_test
17
+
@@ -1,6 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Tabulatrs" do
4
+
5
+ Mongoid.master.collections.select do |collection|
6
+ collection.name !~ /system/
7
+ end.each(&:drop)
4
8
 
5
9
  names = ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur",
6
10
  "adipisicing", "elit", "sed", "eiusmod", "tempor", "incididunt", "labore",
@@ -16,7 +20,7 @@ describe "Tabulatrs" do
16
20
  # General stuf
17
21
  WORKS_IN_GENERAL = CONTAINS_BUTTONS = CONTAINS_COLUMN_HEADERS = CONTAINS_OTHER_CONTROLS = tralse
18
22
  # This fills in the data, so rather not cmment this out
19
- CONTAINS_ACTUAL_DATA = CONTAINS_ASSOC_DATA = CONTAINS_ACTUAL_DATA_MULTIPLE = CONTAINS_DATA_ON_FURTHER_PAGES = true
23
+ CONTAINS_ACTUAL_DATA = CONTAINS_ASSOC_DATA = CONTAINS_ACTUAL_DATA_MULTIPLE = CONTAINS_DATA_ON_FURTHER_PAGES = tralse
20
24
  # Paginatione
21
25
  PAGES_UP_AND_DOWN = JUMPS_TO_CORRECT_PAGE = CHANGES_PAGE_SIZE = tralse
22
26
  # Filters
@@ -24,7 +28,7 @@ describe "Tabulatrs" do
24
28
  # Sorting
25
29
  KNOWS_HOW_TO_SORT = tralse
26
30
  # Statful
27
- SORTS_STATEFULLY = FILTERS_STATEFULLY = SELECTS_STATEFULLY = true
31
+ SORTS_STATEFULLY = FILTERS_STATEFULLY = SELECTS_STATEFULLY = tralse
28
32
  # selecting and batch actions
29
33
  SELECT_BUTTONS_WORK = KNOWS_HOW_TO_SELECT_AND_APPLY_BATCH_ACTIONS = tralse
30
34
 
@@ -33,6 +37,7 @@ describe "Tabulatrs" do
33
37
  tag1 = Tag.create!(:title => 'foo')
34
38
  tag2 = Tag.create!(:title => 'bar')
35
39
  tag3 = Tag.create!(:title => 'fubar')
40
+ ids = []
36
41
 
37
42
  describe "General data" do
38
43
  it "works in general" do
@@ -52,7 +57,7 @@ describe "Tabulatrs" do
52
57
 
53
58
  it "contains column headers" do
54
59
  visit index_simple_products_path
55
- ['Id','Title','Price','Active','Created At','Vendor Name','Tags Title','Tags Count'].each do |n|
60
+ ['Id','Title','Price','Active','Created At','Vendor Created At','Vendor Name','Tags Title','Tags Count'].each do |n|
56
61
  page.should have_content(n)
57
62
  end
58
63
  end if CONTAINS_COLUMN_HEADERS
@@ -69,9 +74,11 @@ describe "Tabulatrs" do
69
74
  page.should have_content("true")
70
75
  page.should have_content("10.0")
71
76
  page.should have_content("--0--")
77
+ #save_and_open_page
72
78
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 1, 1, 0, 1))
73
79
  product.vendor = vendor1
74
80
  product.save!
81
+ ids << product.id
75
82
  visit index_simple_products_path
76
83
  page.should have_content("ven d'or")
77
84
  end if CONTAINS_ACTUAL_DATA
@@ -90,6 +97,7 @@ describe "Tabulatrs" do
90
97
  9.times do |i|
91
98
  product = Product.create!(:title => names[i+1], :active => i.even?, :price => 11.0+i,
92
99
  :description => "blah blah #{i}", :vendor => i.even? ? vendor1 : vendor2)
100
+ ids << product.id
93
101
  visit index_simple_products_path
94
102
  page.should have_content(names[i])
95
103
  page.should have_content((11.0+i).to_s)
@@ -97,10 +105,18 @@ describe "Tabulatrs" do
97
105
  end
98
106
  end if CONTAINS_ACTUAL_DATA_MULTIPLE
99
107
 
108
+ it "contains row identifiers" do
109
+ visit index_simple_products_path
110
+ Product.all.each do |product|
111
+ page.should have_css("#product_#{product.id}")
112
+ end
113
+ end if CONTAINS_ACTUAL_DATA
114
+
100
115
  it "contains the further data on the further pages" do
101
116
  names[10..-1].each_with_index do |n,i|
102
117
  product = Product.create!(:title => n, :active => i.even?, :price => 20.0+i,
103
118
  :description => "blah blah #{i}", :vendor => i.even? ? vendor1 : vendor2)
119
+ ids << product.id
104
120
  visit index_simple_products_path
105
121
  page.should_not have_content(n)
106
122
  page.should_not have_content((30.0+i).to_s)
@@ -207,6 +223,7 @@ describe "Tabulatrs" do
207
223
  click_button("Apply")
208
224
  page.should have_content(str)
209
225
  tot = (names.select do |s| s.match Regexp.new(str) end).length
226
+ #save_and_open_page
210
227
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], [10,tot].min, names.length, 0, tot))
211
228
  end
212
229
  end if FILTERS_WITH_LIKE
@@ -239,6 +256,7 @@ describe "Tabulatrs" do
239
256
  describe "Sorting" do
240
257
  it "knows how to sort" do
241
258
  visit index_sort_products_path
259
+ # save_and_open_page
242
260
  (1..10).each do |i|
243
261
  page.should have_content names[-i]
244
262
  end
@@ -273,21 +291,25 @@ describe "Tabulatrs" do
273
291
  end if SORTS_STATEFULLY
274
292
 
275
293
  it "filters statefully" do
294
+ Capybara.reset_sessions!
276
295
  visit index_stateful_products_path
277
296
  fill_in("product_filter[title]", :with => "lorem")
278
297
  click_button("Apply")
279
298
  visit index_stateful_products_path
299
+ #save_and_open_page
280
300
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 1, names.length, 0, 1))
281
301
  click_button("Reset")
282
302
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, names.length, 0, names.length))
283
303
  end if FILTERS_STATEFULLY
284
-
304
+
285
305
  it "selects statefully" do
286
306
  visit index_stateful_products_path
307
+ fill_in("product_filter[title]", :with => "")
308
+ click_button("Apply")
287
309
  n = names.length
288
310
  (n/10).times do |i|
289
311
  (1..3).each do |j|
290
- check("product_checked_#{10*i+j}")
312
+ check("product_checked_#{ids[10*i+j]}")
291
313
  end
292
314
  click_button("Apply")
293
315
  tot = 3*(i+1)
@@ -342,13 +364,13 @@ describe "Tabulatrs" do
342
364
  click_button("Apply")
343
365
  page.should have_content(sprintf(Tabulatr::TABLE_OPTIONS[:info_text], 10, n, n-tot, n))
344
366
  end if SELECT_BUTTONS_WORK
345
-
367
+
346
368
  it "knows how to select and apply batch actions" do
347
369
  visit index_select_products_path
348
370
  n = names.length
349
371
  (n/10).times do |i|
350
372
  (1..3).each do |j|
351
- check("product_checked_#{10*i+j}")
373
+ check("product_checked_#{ids[10*i+j]}")
352
374
  end
353
375
  click_button("Apply")
354
376
  tot = 3*(i+1)
data/tabulatr.gemspec CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |s|
6
6
  s.name = "tabulatr"
7
7
  s.version = Tabulatr::VERSION.dup
8
8
  s.platform = Gem::Platform::RUBY
9
- s.summary = "A tight DSL to build tables of ActiveRecord models with sorting, pagination, finding/filtering, selecting and batch actions."
9
+ s.summary = "A tight DSL to build tables of ActiveRecord or Mongoid models with sorting, pagination, finding/filtering, selecting and batch actions."
10
10
  s.email = "info@provideal.net"
11
11
  s.homepage = "http://github.com/provideal/tabulatr"
12
- s.description = "A tight DSL to build tables of ActiveRecord models with sorting, pagination, finding/filtering, selecting and batch actions. " +
12
+ s.description = "A tight DSL to build tables of ActiveRecord or Mongoid models with sorting, pagination, finding/filtering, selecting and batch actions. " +
13
13
  "Tries to do for tables what formtastic and simple_form did for forms."
14
14
  s.authors = ['Peter Horn', 'René Sprotte']
15
15
 
metadata CHANGED
@@ -1,8 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabulatr
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.1.3
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
6
10
  platform: ruby
7
11
  authors:
8
12
  - Peter Horn
@@ -11,7 +15,7 @@ autorequire:
11
15
  bindir: bin
12
16
  cert_chain: []
13
17
 
14
- date: 2011-04-11 00:00:00 +02:00
18
+ date: 2011-05-26 00:00:00 +02:00
15
19
  default_executable:
16
20
  dependencies:
17
21
  - !ruby/object:Gem::Dependency
@@ -22,6 +26,9 @@ dependencies:
22
26
  requirements:
23
27
  - - ~>
24
28
  - !ruby/object:Gem::Version
29
+ segments:
30
+ - 3
31
+ - 0
25
32
  version: "3.0"
26
33
  type: :runtime
27
34
  version_requirements: *id001
@@ -33,6 +40,10 @@ dependencies:
33
40
  requirements:
34
41
  - - ">="
35
42
  - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ - 0
46
+ - 2
36
47
  version: 0.0.2
37
48
  type: :runtime
38
49
  version_requirements: *id002
@@ -44,10 +55,14 @@ dependencies:
44
55
  requirements:
45
56
  - - ">="
46
57
  - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 0
61
+ - 1
47
62
  version: 0.0.1
48
63
  type: :runtime
49
64
  version_requirements: *id003
50
- description: A tight DSL to build tables of ActiveRecord models with sorting, pagination, finding/filtering, selecting and batch actions. Tries to do for tables what formtastic and simple_form did for forms.
65
+ description: A tight DSL to build tables of ActiveRecord or Mongoid models with sorting, pagination, finding/filtering, selecting and batch actions. Tries to do for tables what formtastic and simple_form did for forms.
51
66
  email: info@provideal.net
52
67
  executables: []
53
68
 
@@ -88,8 +103,7 @@ files:
88
103
  - lib/tabulatr/tabulatr/data_cell.rb
89
104
  - lib/tabulatr/tabulatr/filter_cell.rb
90
105
  - lib/tabulatr/tabulatr/finder.rb
91
- - lib/tabulatr/tabulatr/finder/find_for_active_record_table.rb
92
- - lib/tabulatr/tabulatr/finder/find_for_mongoid_table.rb
106
+ - lib/tabulatr/tabulatr/finder/find_for_table.rb
93
107
  - lib/tabulatr/tabulatr/formattr.rb
94
108
  - lib/tabulatr/tabulatr/header_cell.rb
95
109
  - lib/tabulatr/tabulatr/paginator.rb
@@ -136,6 +150,7 @@ files:
136
150
  - spec/dummy_app/config/initializers/session_store.rb
137
151
  - spec/dummy_app/config/initializers/tabulatr_settings.rb
138
152
  - spec/dummy_app/config/locales/en.yml
153
+ - spec/dummy_app/config/mongoid.yml
139
154
  - spec/dummy_app/config/routes.rb
140
155
  - spec/dummy_app/db/migrate/20110112151112_create_vendors.rb
141
156
  - spec/dummy_app/db/migrate/20110112151200_create_products.rb
@@ -184,20 +199,24 @@ required_ruby_version: !ruby/object:Gem::Requirement
184
199
  requirements:
185
200
  - - ">="
186
201
  - !ruby/object:Gem::Version
202
+ segments:
203
+ - 0
187
204
  version: "0"
188
205
  required_rubygems_version: !ruby/object:Gem::Requirement
189
206
  none: false
190
207
  requirements:
191
208
  - - ">="
192
209
  - !ruby/object:Gem::Version
210
+ segments:
211
+ - 0
193
212
  version: "0"
194
213
  requirements: []
195
214
 
196
215
  rubyforge_project: tabulatr
197
- rubygems_version: 1.5.0
216
+ rubygems_version: 1.3.7
198
217
  signing_key:
199
218
  specification_version: 3
200
- summary: A tight DSL to build tables of ActiveRecord models with sorting, pagination, finding/filtering, selecting and batch actions.
219
+ summary: A tight DSL to build tables of ActiveRecord or Mongoid models with sorting, pagination, finding/filtering, selecting and batch actions.
201
220
  test_files:
202
221
  - spec/dummy_app/.gitignore
203
222
  - spec/dummy_app/app/controllers/application_controller.rb
@@ -239,6 +258,7 @@ test_files:
239
258
  - spec/dummy_app/config/initializers/session_store.rb
240
259
  - spec/dummy_app/config/initializers/tabulatr_settings.rb
241
260
  - spec/dummy_app/config/locales/en.yml
261
+ - spec/dummy_app/config/mongoid.yml
242
262
  - spec/dummy_app/config/routes.rb
243
263
  - spec/dummy_app/db/migrate/20110112151112_create_vendors.rb
244
264
  - spec/dummy_app/db/migrate/20110112151200_create_products.rb
@@ -1,116 +0,0 @@
1
- #--
2
- # Copyright (c) 2010-2011 Peter Horn, Provideal GmbH
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- # These are extensions for use from ActionController instances
25
- # In a seperate class call only for clearity
26
- module Tabulatr::Finder
27
-
28
- # ----------------------------------------------------------------
29
- # Called if SomeMongoidDocument::find_for_table(params) is called
30
- #
31
- def self.find_for_mongoid_table(klaz, params, opts={})
32
- # firstly, get the conditions from the filters
33
- cname = class_to_param(klaz)
34
- filter_param = (params["#{cname}#{TABLE_FORM_OPTIONS[:filter_postfix]}"] || {})
35
- conditions = filter_param.inject({}) do |c, t|
36
- n, v = t
37
- nc = c
38
- # FIXME n = name_escaping(n)
39
- raise "SECURITY violation, field name is '#{n}'" unless /^[\d\w]+$/.match n
40
- if v.class == String
41
- if v.present?
42
- nc[n.to_sym] = v
43
- end
44
- elsif v.is_a?(Hash)
45
- if v[:like]
46
- if v[:like].present?
47
- nc[n.to_sym] = Regexp.new("#{v[:like]}")
48
- end
49
- else
50
- nc[n.to_sym.gte] = "#{v[:from]}" if v[:from].present?
51
- nc[n.to_sym.lte] = "#{v[:to]}" if v[:to].present?
52
- end
53
- else
54
- raise "Wrong filter type: #{v.class}"
55
- end
56
- nc
57
- end
58
-
59
- # secondly, find the order_by stuff
60
- # FIXME: Implement me! PLEEEZE!
61
- sortparam = params["#{cname}#{TABLE_FORM_OPTIONS[:sort_postfix]}"]
62
- if sortparam
63
- if sortparam[:_resort]
64
- order_by = sortparam[:_resort].first.first
65
- order_direction = sortparam[:_resort].first.last.first.first
66
- else
67
- order_by = sortparam.first.first
68
- order_direction = sortparam.first.last.first.first
69
- end
70
- raise "SECURITY violation, sort field name is '#{n}'" unless /^[\w]+$/.match order_direction
71
- raise "SECURITY violation, sort field name is '#{n}'" unless /^[\d\w]+$/.match order_by
72
- order = [order_by, order_direction]
73
- else
74
- order = nil
75
- end
76
-
77
- # thirdly, get the pagination data
78
- paginate_options = PAGINATE_OPTIONS.merge(opts).
79
- merge(params["#{cname}#{TABLE_FORM_OPTIONS[:pagination_postfix]}"] || {})
80
- page = paginate_options[:page].to_i
81
- page += 1 if paginate_options[:page_right]
82
- page -= 1 if paginate_options[:page_left]
83
- pagesize = paginate_options[:pagesize].to_f
84
- c = klaz.count :conditions => conditions
85
- pages = (c/pagesize).ceil
86
- page = [1, [page, pages].min].max
87
-
88
- # Now, actually find the stuff
89
- found = klaz.find(:conditions => conditions)
90
- found = found.order_by([order]) if order
91
- found = found.paginate(:page => page, :per_page => pagesize)
92
-
93
- # finally, inject methods to retrieve the current 'settings'
94
- found.define_singleton_method(fio[:filters]) do filter_param end
95
- found.define_singleton_method(fio[:classname]) do cname end
96
- found.define_singleton_method(fio[:pagination]) do
97
- {:page => page, :pagesize => pagesize, :count => c, :pages => pages,
98
- :pagesizes => paginate_options[:pagesizes]}
99
- end
100
- found.define_singleton_method(fio[:sorting]) do
101
- order ? { :by => order_by, :direction => order_direction } : nil
102
- end
103
- checked_param = params["#{cname}#{TABLE_FORM_OPTIONS[:checked_postfix]}"]
104
- checked_ids = checked_param[:checked].split(TABLE_FORM_OPTIONS[:checked_separator])
105
- new_ids = checked_param[:current_page] || []
106
- selected_ids = checked_ids + new_ids
107
- ids = found.map { |r| r.id.to_s }
108
- checked_ids = selected_ids - ids
109
- found.define_singleton_method(fio[:checked]) do
110
- { :selected => selected_ids,
111
- :checked_ids => checked_ids.join(TABLE_FORM_OPTIONS[:checked_separator]) }
112
- end
113
- found
114
- end
115
-
116
- end