datagrid 0.9.3 → 1.0.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.
Files changed (39) hide show
  1. data/Readme.markdown +6 -4
  2. data/VERSION +1 -1
  3. data/app/assets/stylesheets/datagrid.css.sass +132 -0
  4. data/app/views/datagrid/_form.html.erb +5 -2
  5. data/app/views/datagrid/_order_for.html.erb +2 -2
  6. data/app/views/datagrid/_table.html.erb +1 -1
  7. data/datagrid.gemspec +10 -3
  8. data/lib/datagrid.rb +1 -0
  9. data/lib/datagrid/column_names_attribute.rb +38 -7
  10. data/lib/datagrid/columns.rb +38 -4
  11. data/lib/datagrid/columns/column.rb +29 -1
  12. data/lib/datagrid/drivers/abstract_driver.rb +8 -0
  13. data/lib/datagrid/drivers/active_record.rb +29 -1
  14. data/lib/datagrid/drivers/array.rb +14 -2
  15. data/lib/datagrid/drivers/mongo_mapper.rb +8 -0
  16. data/lib/datagrid/drivers/mongoid.rb +9 -1
  17. data/lib/datagrid/filters.rb +24 -6
  18. data/lib/datagrid/filters/base_filter.rb +42 -14
  19. data/lib/datagrid/filters/boolean_enum_filter.rb +1 -1
  20. data/lib/datagrid/filters/dynamic_filter.rb +57 -0
  21. data/lib/datagrid/filters/enum_filter.rb +4 -21
  22. data/lib/datagrid/filters/select_options.rb +26 -0
  23. data/lib/datagrid/form_builder.rb +41 -8
  24. data/lib/datagrid/helper.rb +2 -1
  25. data/lib/datagrid/i18n.rb +0 -0
  26. data/lib/datagrid/locale/en.yml +28 -0
  27. data/lib/datagrid/ordering.rb +33 -19
  28. data/lib/datagrid/utils.rb +8 -9
  29. data/spec/datagrid/column_names_attribute_spec.rb +44 -1
  30. data/spec/datagrid/columns_spec.rb +16 -0
  31. data/spec/datagrid/filters/dynamic_filter_spec.rb +37 -0
  32. data/spec/datagrid/filters/integer_filter_spec.rb +18 -0
  33. data/spec/datagrid/filters/string_filter_spec.rb +25 -0
  34. data/spec/datagrid/filters_spec.rb +15 -1
  35. data/spec/datagrid/form_builder_spec.rb +83 -0
  36. data/spec/datagrid/helper_spec.rb +1 -0
  37. data/spec/datagrid/ordering_spec.rb +41 -1
  38. data/spec/datagrid/utils_spec.rb +7 -2
  39. metadata +11 -4
@@ -57,6 +57,7 @@ module Datagrid
57
57
 
58
58
  # Provides access to datagrid columns data.
59
59
  #
60
+ # # Suppose that <tt>grid</tt> has first_name and last_name columns
60
61
  # <%= datagrid_row(grid, user) do |row| %>
61
62
  # <tr>
62
63
  # <td><%= row.first_name %></td>
@@ -73,7 +74,7 @@ module Datagrid
73
74
  end
74
75
  end
75
76
 
76
- class HtmlRow
77
+ class HtmlRow #:nodoc:
77
78
  def initialize(context, grid, asset)
78
79
  @context = context
79
80
  @grid = grid
File without changes
@@ -0,0 +1,28 @@
1
+ en:
2
+ datagrid:
3
+ no_results:
4
+ "&mdash;&mdash;"
5
+ table:
6
+ order:
7
+ asc: "&uarr;"
8
+ desc: "&darr;"
9
+ form:
10
+ search: "Search"
11
+ reset: "Reset"
12
+ filters:
13
+ integer:
14
+ range_separator:
15
+ "<span class=\"separator integer\"> - </span>"
16
+ date:
17
+ range_separator:
18
+ "<span class=\"separator date\"> - </span>"
19
+
20
+ eboolean:
21
+ "yes": "Yes"
22
+ "no": "No"
23
+ dynamic:
24
+ operations:
25
+ ">=": ">="
26
+ "<=": "<="
27
+ "=": "="
28
+ "=~": "=~"
@@ -17,10 +17,8 @@ module Datagrid
17
17
  unless column
18
18
  order_unsupported(value, "no column #{value} in #{self.class}")
19
19
  end
20
- unless column.order
21
- order_unsupported(
22
- name, "#{self.class}##{name} don't support order"
23
- )
20
+ unless column.supports_order?
21
+ order_unsupported(column.name, "column don't support order" )
24
22
  end
25
23
  value
26
24
  else
@@ -49,25 +47,41 @@ module Datagrid
49
47
  module InstanceMethods
50
48
 
51
49
  def assets # :nodoc:
52
- result = super
53
- if order
54
- column = column_by_name(order)
55
- result = apply_order(result, column)
56
- end
57
- result
50
+ apply_order(super)
51
+ end
52
+
53
+ # Returns a column definition that is currently used to order assets
54
+ #
55
+ # class MyGrid
56
+ # scope { Model }
57
+ # column(:id)
58
+ # column(:name)
59
+ # end
60
+ # MyGrid.new(:order => "name").order_column # => #<Column name: "name", ...>
61
+ #
62
+ def order_column
63
+ column_by_name(order)
58
64
  end
59
65
 
60
66
  private
61
67
 
62
- def apply_order(assets, column)
63
- if descending?
64
- if column.order_desc
65
- apply_asc_order(assets, column.order_desc)
66
- else
67
- apply_desc_order(assets, column.order)
68
+ def apply_order(assets)
69
+ return assets unless order
70
+ if order_column.order_by_value?
71
+ assets = assets.sort_by do |asset|
72
+ order_column.order_by_value(asset, self)
68
73
  end
74
+ descending? ? assets.reverse : assets
69
75
  else
70
- apply_asc_order(assets, column.order)
76
+ if descending?
77
+ if order_column.order_desc
78
+ apply_asc_order(assets, order_column.order_desc)
79
+ else
80
+ apply_desc_order(assets, order_column.order)
81
+ end
82
+ else
83
+ apply_asc_order(assets, order_column.order)
84
+ end
71
85
  end
72
86
  end
73
87
 
@@ -90,7 +104,7 @@ module Datagrid
90
104
  def reverse_order(assets)
91
105
  driver.reverse_order(assets)
92
106
  rescue NotImplementedError
93
- self.class.order_unsupported("Your ORM do not support reverse order: please specify :order_desc option manually")
107
+ self.class.order_unsupported(order_column.name, "Your ORM do not support reverse order: please specify :order_desc option manually")
94
108
  end
95
109
 
96
110
  def apply_block_order(assets, order)
@@ -100,7 +114,7 @@ module Datagrid
100
114
  when 1
101
115
  order.call(assets)
102
116
  else
103
- self.class.order_unsupported("Order option proc can not handle more than one argument")
117
+ self.class.order_unsupported(order_column.name, "Order option proc can not handle more than one argument")
104
118
  end
105
119
  end
106
120
  end # InstanceMethods
@@ -1,5 +1,5 @@
1
- module Datagrid
2
- module Utils
1
+ module Datagrid
2
+ module Utils # :nodoc:
3
3
  class << self
4
4
 
5
5
 
@@ -9,14 +9,13 @@ module Datagrid
9
9
  TRUTH.include?(value)
10
10
  end
11
11
 
12
- def warn_once(message)
12
+ def warn_once(message, delay = 5)
13
13
  @warnings ||= {}
14
- if @warnings[message]
15
- false
16
- else
17
- warn message
18
- @warnings[message] = true
19
- end
14
+ timestamp = @warnings[message]
15
+ return false if timestamp && timestamp >= Time.now - delay
16
+ warn message
17
+ @warnings[message] = Time.now
18
+ true
20
19
  end
21
20
 
22
21
  def add_html_classes(options, *classes)
@@ -2,6 +2,49 @@ require "spec_helper"
2
2
 
3
3
  describe Datagrid::ColumnNamesAttribute do
4
4
 
5
- describe ".column_names_filter" do
5
+ let(:column_names_filter_options) do
6
+ {}
7
+ end
8
+
9
+ let(:report) do
10
+ options = column_names_filter_options
11
+ test_report do
12
+ scope { Entry }
13
+ column_names_filter(options)
14
+ column(:id)
15
+ column(:name, :mandatory => true)
16
+ column(:category)
17
+ end
18
+ end
19
+ subject { report }
20
+
21
+
22
+ let!(:entry) do
23
+ Entry.create!(:name => 'hello', :category => 'greeting')
24
+ end
25
+
26
+ it "should work" do
27
+ subject.column_names = [:id]
28
+ subject.mandatory_columns.map(&:name).should == [:name]
29
+ subject.optional_columns.map(&:name).should == [:id, :category]
30
+ subject.data.should == [["Id", "Name"], [entry.id, "hello"]]
31
+ columns_filter = subject.filter_by_name(:column_names)
32
+ columns_filter.should_not be_nil
33
+ columns_filter.select(subject).should == [["Id", :id], ["Category", :category]]
34
+ end
35
+
36
+ it "should show only mandatory columns by default" do
37
+ subject.row_for(entry).should == [ "hello" ]
38
+ subject.column_names = ["name", "category"]
39
+ subject.row_for(entry).should == ["hello", "greeting"]
40
+ end
41
+
42
+
43
+ context "when default option is passed to column_names_filter" do
44
+ let(:column_names_filter_options) do
45
+ { :default => [:id] }
46
+ end
47
+ its(:data) { should == [["Id", "Name"], [entry.id, 'hello']] }
48
+
6
49
  end
7
50
  end
@@ -161,4 +161,20 @@ describe Datagrid::Columns do
161
161
  report.rows.should == [["Hello World"]]
162
162
  end
163
163
  end
164
+
165
+ describe ".default_column_options" do
166
+ it "should pass default options to each column definition" do
167
+ report = test_report do
168
+ scope {Entry}
169
+ self.default_column_options = {:order => false}
170
+ column(:id)
171
+ column(:name, :order => "name")
172
+ end
173
+ first = Entry.create(:name => '1st')
174
+ second = Entry.create(:name => '2nd')
175
+ proc { report.attributes = {:order => :id} }.should raise_error(Datagrid::OrderUnsupported)
176
+ report.attributes = {:order => :name, :descending => true}
177
+ report.assets.should == [second, first]
178
+ end
179
+ end
164
180
  end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Datagrid::Filters::DynamicFilter do
5
+ let(:report) do
6
+ test_report do
7
+ scope {Entry}
8
+ filter(:condition, :dynamic)
9
+ end
10
+ end
11
+
12
+ it "should support = operation" do
13
+ report.condition = [:name, "=", "hello"]
14
+ report.assets.should include(Entry.create!(:name => 'hello'))
15
+ report.assets.should_not include(Entry.create!(:name => 'bye'))
16
+ end
17
+ it "should blank value" do
18
+ report.condition = [:name, "=", ""]
19
+ report.assets.should include(Entry.create!(:name => 'hello'))
20
+ end
21
+ it "should support =~ operation" do
22
+ report.condition = [:name, "=~", "ell"]
23
+ report.assets.should include(Entry.create!(:name => 'hello'))
24
+ report.assets.should_not include(Entry.create!(:name => 'bye'))
25
+ end
26
+ it "should support >= operation" do
27
+ report.condition = [:group_id, ">=", 2]
28
+ report.assets.should include(Entry.create!(:group_id => 3))
29
+ report.assets.should_not include(Entry.create!(:group_id => 1))
30
+ end
31
+ it "should support <= operation" do
32
+ report.condition = [:group_id, "<=", 2]
33
+ report.assets.should include(Entry.create!(:group_id => 1))
34
+ report.assets.should_not include(Entry.create!(:group_id => 3))
35
+ end
36
+
37
+ end
@@ -103,5 +103,23 @@ describe Datagrid::Filters::IntegerFilter do
103
103
  report.assets.should include(Entry.create!(:group => Group.create!(:rating => 5)))
104
104
  end
105
105
 
106
+ it "should support multiple values" do
107
+ report = test_report(:group_id => "1,2") do
108
+ scope {Entry}
109
+ filter(:group_id, :string, :multiple => true)
110
+ end
111
+ report.assets.should include(Entry.create!( :group_id => 1))
112
+ report.assets.should include(Entry.create!( :group_id => 2))
113
+ report.assets.should_not include(Entry.create!( :group_id => 3))
114
+ end
115
+ it "should support custom separator multiple values" do
116
+ report = test_report(:group_id => "1|2") do
117
+ scope {Entry}
118
+ filter(:group_id, :string, :multiple => '|')
119
+ end
120
+ report.assets.should include(Entry.create!( :group_id => 1))
121
+ report.assets.should include(Entry.create!( :group_id => 2))
122
+ report.assets.should_not include(Entry.create!( :group_id => 3))
123
+ end
106
124
 
107
125
  end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Datagrid::Filters::StringFilter do
5
+
6
+ it "should support multiple values" do
7
+ report = test_report(:name => "one,two") do
8
+ scope {Entry}
9
+ filter(:name, :string, :multiple => true)
10
+ end
11
+ report.assets.should include(Entry.create!( :name => "one"))
12
+ report.assets.should include(Entry.create!( :name => "two"))
13
+ report.assets.should_not include(Entry.create!( :name => "three"))
14
+ end
15
+ it "should support custom separator multiple values" do
16
+ report = test_report(:name => "one,1|two,2") do
17
+ scope {Entry}
18
+ filter(:name, :string, :multiple => '|')
19
+ end
20
+ report.assets.should include(Entry.create!( :name => "one,1"))
21
+ report.assets.should include(Entry.create!( :name => "two,2"))
22
+ report.assets.should_not include(Entry.create!( :name => "one"))
23
+ end
24
+
25
+ end
@@ -75,7 +75,7 @@ describe Datagrid::Filters do
75
75
  $FILTER_PERFORMED = false
76
76
  report = test_report(:name => value) do
77
77
  scope {Entry}
78
- filter(:name, options) do |value|
78
+ filter(:name, options) do |_|
79
79
  $FILTER_PERFORMED = true
80
80
  self
81
81
  end
@@ -161,4 +161,18 @@ describe Datagrid::Filters do
161
161
  Entry.create!(:created_at => 3.days.ago)
162
162
  grid.assets.should_not be_empty
163
163
  end
164
+
165
+ describe "#filter_by" do
166
+ it "should allow partial filtering" do
167
+ grid = test_report do
168
+ scope {Entry}
169
+ filter(:id)
170
+ filter(:name)
171
+ end
172
+ e = Entry.create!(:name => 'hello')
173
+ grid.attributes = {:id => -1, :name => 'hello'}
174
+ grid.assets.should be_empty
175
+ grid.filter_by(:name).should_not be_empty
176
+ end
177
+ end
164
178
  end
@@ -245,6 +245,19 @@ describe Datagrid::FormBuilder do
245
245
  let(:_filter) { :name }
246
246
 
247
247
  it {should equal_to_dom('<input class="name string_filter" id="report_name" name="report[name]" size="30" type="text">')}
248
+
249
+ context "when multiple option is set" do
250
+ let(:_grid) do
251
+ test_report(:name => "one,two") do
252
+ scope {Entry}
253
+ filter(:name, :string, :multiple => true)
254
+ end
255
+ end
256
+
257
+ let(:_filter) { :name }
258
+
259
+ it {should equal_to_dom('<input class="name string_filter" id="report_name" name="report[name]" size="30" type="text" value="one,two">')}
260
+ end
248
261
  end
249
262
 
250
263
  context "with non multiple filter" do
@@ -332,8 +345,78 @@ describe Datagrid::FormBuilder do
332
345
 
333
346
  it { should equal_to_dom(expected_html) }
334
347
  end
348
+
349
+ context "with dynamic filter" do
350
+ let(:filter_options) do
351
+ {}
352
+ end
353
+
354
+ let(:_grid) do
355
+ options = filter_options
356
+ test_report do
357
+ scope {Entry}
358
+ filter(:condition, :dynamic, options)
359
+ end
360
+ end
361
+ let(:_filter) { :condition }
362
+ context "with no options" do
363
+ let(:expected_html) do
364
+ <<-HTML
365
+ <select class="condition dynamic_filter field" id="report_condition" name="report[condition][]"><option value="id">Id</option>
366
+ <option value="group_id">Group</option>
367
+ <option value="name">Name</option>
368
+ <option value="category">Category</option>
369
+ <option value="access_level">Access level</option>
370
+ <option value="pet">Pet</option>
371
+ <option value="disabled">Disabled</option>
372
+ <option value="confirmed">Confirmed</option>
373
+ <option value="shipping_date">Shipping date</option>
374
+ <option value="created_at">Created at</option>
375
+ <option value="updated_at">Updated at</option></select><select class="condition dynamic_filter operation" id="report_condition" name="report[condition][]"><option value="=">=</option>
376
+ <option value="=~">=~</option>
377
+ <option value="&gt;=">&gt;=</option>
378
+ <option value="&lt;=">&lt;=</option></select><input class="condition dynamic_filter value" id="report_condition" name="report[condition][]" size="30" type="text">
379
+ HTML
380
+ end
381
+ it {should equal_to_dom(expected_html)}
382
+
383
+ end
384
+ context "when default option passed" do
385
+ let(:filter_options) do
386
+ {:select => [:id, :name], :default => [:id, '>=', 1]}
387
+ end
388
+ let(:expected_html) do
389
+ <<-HTML
390
+ <select class="condition dynamic_filter field" id="report_condition" name="report[condition][]"><option value="id" selected>id</option>
391
+ <option value="name">name</option></select><select class="condition dynamic_filter operation" id="report_condition" name="report[condition][]"><option value="=">=</option>
392
+ <option value="=~">=~</option>
393
+ <option value="&gt;=" selected>&gt;=</option>
394
+ <option value="&lt;=">&lt;=</option></select><input class="condition dynamic_filter value" id="report_condition" name="report[condition][]" size="30" type="text" value="1">
395
+ HTML
396
+ end
397
+ it {should equal_to_dom(expected_html)}
398
+
399
+ end
400
+ context "when select option passed" do
401
+ let(:filter_options) do
402
+ {:select => [:id, :name]}
403
+ end
404
+ let(:expected_html) do
405
+ <<-HTML
406
+ <select class="condition dynamic_filter field" id="report_condition" name="report[condition][]"><option value="id">id</option>
407
+ <option value="name">name</option></select><select class="condition dynamic_filter operation" id="report_condition" name="report[condition][]"><option value="=">=</option>
408
+ <option value="=~">=~</option>
409
+ <option value="&gt;=">&gt;=</option>
410
+ <option value="&lt;=">&lt;=</option></select><input class="condition dynamic_filter value" id="report_condition" name="report[condition][]" size="30" type="text">
411
+ HTML
412
+ end
413
+ it {should equal_to_dom(expected_html)}
414
+
415
+ end
416
+ end
335
417
  end
336
418
 
419
+
337
420
  describe ".datagrid_label" do
338
421
  let(:_grid) do
339
422
  test_report do