magic_grid 0.10.4 → 0.11.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.
@@ -95,10 +95,10 @@ $(function () {
95
95
  current = $input.data("current"),
96
96
  value = $input.val(),
97
97
  length = value.length,
98
- base_url = base_url.split("?", 2)[0],
98
+ base_url_path = base_url.split("?", 2)[0],
99
99
  relevant = is_manual || (value != current && (length >= minLength || length == 0)),
100
100
  pos = $input.getCursor(),
101
- url = base_url + '?' + $.param($grid.getGridParams());
101
+ url = base_url_path + '?' + $.param($grid.getGridParams());
102
102
  clearTimeout(timer);
103
103
  if (relevant) {
104
104
  $grid.trigger("magic_grid:loading");
data/lib/magic_grid.rb CHANGED
@@ -1,7 +1 @@
1
- module MagicGrid
2
- if ::Rails.version < "3.1"
3
- require 'magic_grid/railtie'
4
- else
5
- require 'magic_grid/engine'
6
- end
7
- end
1
+ require 'magic_grid/engine'
@@ -0,0 +1,187 @@
1
+ require 'active_support/core_ext'
2
+ require 'magic_grid/logger'
3
+
4
+ module MagicGrid
5
+ class Collection
6
+
7
+ def initialize(collection, grid)
8
+ @collection = collection
9
+ @grid = grid
10
+ @current_page = 1
11
+ @sorts = []
12
+ @filter_callbacks = []
13
+ @filters = []
14
+ @searches = []
15
+ @post_filters = []
16
+ @post_filter_callbacks = []
17
+ @paginations = []
18
+ end
19
+
20
+ delegate :map, :count, :to => :collection
21
+
22
+ attr_accessor :grid
23
+ attr_reader :current_page, :original_count, :total_pages
24
+
25
+ def self.[](collection, grid)
26
+ if collection.is_a?(self)
27
+ collection.grid = grid
28
+ collection
29
+ else
30
+ Collection.new(collection, grid)
31
+ end
32
+ end
33
+
34
+ def quote_column_name(col)
35
+ @collection.connection.quote_column_name(col.to_s)
36
+ end
37
+
38
+ def search_using_builtin(collection, q)
39
+ collection.__send__(@grid.options[:search_method], q)
40
+ end
41
+
42
+ def search_using_where(collection, q)
43
+ result = collection
44
+ search_cols = @grid.options[:searchable].map do |searchable|
45
+ case searchable
46
+ when Symbol
47
+ known = @grid.columns.find {|col| col[:col] == searchable}
48
+ if known and known.key?(:sql)
49
+ known[:sql]
50
+ else
51
+ "#{@collection.table_name}.#{quote_column_name(searchable)}"
52
+ end
53
+ when Integer
54
+ @grid.columns[searchable][:sql]
55
+ when String
56
+ searchable
57
+ else
58
+ raise "Searchable must be identifiable"
59
+ end
60
+ end
61
+
62
+ unless search_cols.empty?
63
+ begin
64
+ clauses = search_cols.map {|c| c << " LIKE :search" }.join(" OR ")
65
+ result = collection.where(clauses, {:search => "%#{q}%"})
66
+ rescue
67
+ MagicGrid.logger.debug "Given collection doesn't respond to :where well"
68
+ end
69
+ end
70
+ result
71
+ end
72
+
73
+ def sortable?
74
+ @collection.respond_to?(:order)
75
+ end
76
+
77
+ def apply_sort(col, dir)
78
+ @sorts << "#{col} #{dir}"
79
+ self
80
+ end
81
+
82
+ def searchable?
83
+ filterable? or @collection.respond_to? @grid.options[:search_method]
84
+ end
85
+
86
+ def apply_search(q)
87
+ @searches << q
88
+ self
89
+ end
90
+
91
+ def perform_search(collection, q)
92
+ search_using_builtin(collection, q)
93
+ rescue
94
+ MagicGrid.logger.debug "Given collection doesn't respond to #{@grid.options[:search_method]} well"
95
+ search_using_where(collection, q)
96
+ end
97
+
98
+ def filterable?
99
+ @collection.respond_to? :where
100
+ end
101
+
102
+ def apply_filter(filters = {})
103
+ if @collection.respond_to? :where
104
+ @filters << filters
105
+ end
106
+ self
107
+ end
108
+
109
+ def apply_filter_callback(callback)
110
+ if callback.respond_to? :call
111
+ @filter_callbacks << callback
112
+ end
113
+ self
114
+ end
115
+
116
+ def has_post_filter?
117
+ @collection.respond_to? :post_filter
118
+ end
119
+
120
+ def apply_post_filter
121
+ @post_filters << :post_filter
122
+ self
123
+ end
124
+
125
+ def apply_pagination(current_page, per_page)
126
+ if per_page
127
+ @paginations << {:current_page => current_page, :per_page => per_page}
128
+ end
129
+ self
130
+ end
131
+
132
+ def perform_pagination(collection, current_page, per_page)
133
+ @original_count = @collection.count
134
+ @total_pages = @original_count / per_page
135
+ @current_page = current_page
136
+ if collection.respond_to? :paginate
137
+ collection = collection.paginate(:page => current_page,
138
+ :per_page => per_page)
139
+ elsif collection.respond_to? :page
140
+ collection = collection.page(current_page).per(per_page)
141
+ elsif collection.is_a?(Array) and Module.const_defined?(:Kaminari)
142
+ collection = Kaminari.paginate_array(collection).page(current_page).per(per_page)
143
+ else
144
+ collection = collection.to_enum
145
+ collection = collection.each_slice(per_page)
146
+ collection = collection.drop(current_page - 1)
147
+ collection = collection.first.to_a
148
+ class << collection
149
+ attr_accessor :current_page, :total_pages, :original_count
150
+ end
151
+ end
152
+ collection
153
+ end
154
+
155
+ def apply_all_operations(collection)
156
+ @sorts.each do |ordering|
157
+ collection = collection.order(ordering)
158
+ end
159
+ @filter_callbacks.each do |callback|
160
+ collection = callback.call(collection)
161
+ end
162
+ @filters.each do |hsh|
163
+ collection = collection.where(hsh)
164
+ end
165
+ @searches.each do |query|
166
+ collection = perform_search(collection, query)
167
+ end
168
+ @post_filters.each do |filter|
169
+ collection = collection.__send__(filter)
170
+ end
171
+ @post_filter_callbacks.each do |callback|
172
+ collection = callback.call(collection)
173
+ end
174
+ @paginations.each do |params|
175
+ collection = perform_pagination(collection, params[:current_page], params[:per_page])
176
+ end
177
+ collection
178
+ end
179
+
180
+ def collection
181
+ @reduced_collection ||= apply_all_operations(@collection)
182
+ end
183
+
184
+
185
+
186
+ end
187
+ end
@@ -1,18 +1,22 @@
1
- #require 'will_paginate/view_helpers/action_view'
1
+ require 'magic_grid/logger'
2
+ require 'magic_grid/collection'
2
3
 
3
4
  module MagicGrid
4
5
  class Definition
5
- #include WillPaginate::ActionView
6
- attr_accessor :columns, :collection, :magic_id, :options, :params,
6
+ attr_reader :columns, :magic_id, :options, :params,
7
7
  :current_sort_col, :current_order, :default_order, :per_page
8
8
 
9
+ def collection
10
+ @collection.collection
11
+ end
12
+
9
13
  DEFAULTS = {
10
14
  :class => [],
11
15
  :top_pager => false,
12
16
  :bottom_pager => true,
13
17
  :remote => false,
14
18
  :per_page => 30,
15
- :searchable => false,
19
+ :searchable => [],
16
20
  :search_method => :search,
17
21
  :min_search_length => 3,
18
22
  :id => false,
@@ -56,13 +60,13 @@ module MagicGrid
56
60
  @default_order = @options[:default_order]
57
61
  @params = controller && controller.params || {}
58
62
  @per_page = @options[:per_page]
59
- @collection = collection
63
+ @collection = Collection[collection, self]
60
64
  begin
61
65
  #if @collection.respond_to? :table
62
66
  table_name = @collection.quoted_table_name
63
67
  table_columns = @collection.table.columns.map {|c| c.name}
64
68
  rescue
65
- Rails.logger.debug "Given collection doesn't respond to :table well"
69
+ MagicGrid.logger.debug "Given collection doesn't respond to :table well"
66
70
  table_name = nil
67
71
  table_columns = @columns.each_index.to_a
68
72
  end
@@ -90,107 +94,69 @@ module MagicGrid
90
94
  @magic_id << @collection.to_sql.hash.abs.to_s(36) if @collection.respond_to? :to_sql
91
95
  end
92
96
  @current_sort_col = sort_col_i = param(:col, @options[:default_col]).to_i
93
- if @collection.respond_to?(:order) and @columns.count > sort_col_i and @columns[sort_col_i].has_key?(:sql)
97
+ if @collection.sortable? and @columns.count > sort_col_i and @columns[sort_col_i].has_key?(:sql)
94
98
  sort_col = @columns[sort_col_i][:sql]
95
99
  @current_order = order(param(:order, @default_order))
96
100
  sort_dir = order_sql(@current_order)
97
- @collection = @collection.order("#{sort_col} #{sort_dir}")
101
+ @collection.apply_sort(sort_col, sort_dir)
98
102
  else
99
- Rails.logger.debug "#{self.class.name}: Ignoring sorting on non-AR collection"
103
+ MagicGrid.logger.debug "#{self.class.name}: Ignoring sorting on non-AR collection"
100
104
  end
101
105
 
102
- @options[:searchable] = [] if @options[:searchable] and not @options[:searchable].kind_of? Array
103
-
104
- if @collection.respond_to?(:where) or @options[:listener_handler].respond_to?(:call)
106
+ if @collection.filterable? or @options[:listener_handler].respond_to?(:call)
105
107
  if @options[:listener_handler].respond_to? :call
106
- @collection = @options[:listener_handler].call(@collection)
108
+ @collection.apply_filter_callback @options[:listener_handler]
107
109
  else
108
110
  @options[:listeners].each_pair do |key, value|
109
111
  if @params[value] and not @params[value].to_s.empty?
110
- @collection = @collection.where(value => @params[value])
112
+ @collection.apply_filter(value => @params[value])
111
113
  end
112
114
  end
113
115
  end
114
116
  else
115
117
  unless @options[:listeners].empty?
116
- Rails.logger.warn "#{self.class.name}: Ignoring listener on dumb collection"
118
+ MagicGrid.logger.warn "#{self.class.name}: Ignoring listener on dumb collection"
117
119
  @options[:listeners] = {}
118
120
  end
119
121
  end
122
+
123
+ @options[:searchable] = Array(@options[:searchable])
120
124
  @options[:current_search] ||= param(:q)
121
- if (@collection.respond_to?(:where) or
122
- (@options[:search_method] and @collection.respond_to?(@options[:search_method])))
123
- if param(:q) and not param(:q).empty? and @options[:searchable]
124
- orig_collection = @collection
125
- begin
126
- @collection = @collection.__send__(@options[:search_method], param(:q))
127
- rescue
128
- Rails.logger.debug "Given collection doesn't respond to #{@options[:search_method]} well"
129
- @collection = orig_collection
130
- search_cols = @options[:searchable].map do |searchable|
131
- case searchable
132
- when Symbol
133
- known = @columns.find {|col| col[:col] == searchable}
134
- if known and known.key?(:sql)
135
- known[:sql]
136
- else
137
- "#{table_name}.#{@collection.connection.quote_column_name(searchable.to_s)}"
138
- end
139
- when Integer
140
- @columns[searchable][:sql]
141
- when String
142
- searchable
143
- else
144
- raise "Searchable must be identifiable"
145
- end
146
- end
147
- unless search_cols.empty?
148
- begin
149
- clauses = search_cols.map {|c| c << " LIKE :search" }.join(" OR ")
150
- @collection = @collection.where(clauses, {:search => "%#{param(:q)}%"})
151
- rescue
152
- Rails.logger.debug "Given collection doesn't respond to :where well"
153
- @collection = orig_collection
154
- end
155
- end
156
- end
125
+ if @collection.searchable?
126
+ if param(:q) and not param(:q).empty? and not @options[:searchable].empty?
127
+ @collection.apply_search(param(:q))
157
128
  end
158
129
  else
159
- if @options[:searchable] or param(:q)
160
- Rails.logger.warn "#{self.class.name}: Ignoring searchable fields on non-AR collection"
130
+ if not @options[:searchable].empty? or param(:q)
131
+ MagicGrid.logger.warn "#{self.class.name}: Ignoring searchable fields on non-AR collection"
161
132
  end
162
- @options[:searchable] = false
163
- end
164
- if not @options[:searcher] and @options[:searchable]
165
- @options[:needs_searcher] = true
166
- @options[:searcher] = param_key(:searcher)
133
+ @options[:searchable] = []
167
134
  end
135
+
168
136
  # Do collection filter first, may convert from AR to Array
169
- if @options[:collection_post_filter?] and @collection.respond_to?(:post_filter)
170
- @collection = @collection.post_filter(controller)
137
+ if @options[:collection_post_filter?] and @collection.has_post_filter?
138
+ @collection.apply_post_filter
171
139
  end
172
140
  if @options[:post_filter] and @options[:post_filter].respond_to?(:call)
173
- @collection = @options[:post_filter].call(@collection)
141
+ @collection.apply_filter_callback @options[:post_filter]
174
142
  end
175
143
  # Paginate at the very end, after all sorting, filtering, etc..
176
- if @per_page
177
- if @collection.respond_to? :paginate
178
- @collection = @collection.paginate(:page => current_page,
179
- :per_page => @per_page)
180
- elsif @collection.respond_to? :page
181
- @collection = @collection.page(current_page).per(@per_page)
182
- elsif @collection.is_a?(Array) and Module.const_defined?(:Kaminari)
183
- @collection = Kaminari.paginate_array(@collection).page(current_page).per(@per_page)
184
- else
185
- original = @collection
186
- @collection = @collection.to_enum.each_slice(@per_page).drop(current_page - 1).first.to_a
187
- class << @collection
188
- attr_accessor :current_page, :total_pages, :original_count
189
- end
190
- @collection.current_page = current_page
191
- @collection.original_count = original.count
192
- @collection.total_pages = original.count / @per_page
193
- end
144
+ @collection.apply_pagination(current_page, @per_page)
145
+ end
146
+
147
+ def searchable?
148
+ @collection.searchable? and not @options[:searchable].empty?
149
+ end
150
+
151
+ def needs_searcher?
152
+ @options[:needs_searcher] or (searchable? and not @options[:searcher])
153
+ end
154
+
155
+ def searcher
156
+ if needs_searcher?
157
+ param_key(:searcher)
158
+ else
159
+ @options[:searcher]
194
160
  end
195
161
  end
196
162
 
@@ -1,3 +1,5 @@
1
+ require 'magic_grid/logger'
2
+
1
3
  module MagicGrid
2
4
  class Engine < ::Rails::Engine
3
5
  # Once in production, on every page view in development
@@ -10,5 +12,9 @@ module MagicGrid
10
12
  # Provide some fallback translations that users can override
11
13
  app.config.i18n.load_path += Dir.glob(File.expand_path('../../locales/*.{rb,yml}', __FILE__))
12
14
  end
15
+
16
+ initializer "Rails logger" do
17
+ MagicGrid.logger = Rails.logger
18
+ end
13
19
  end
14
20
  end
@@ -4,29 +4,23 @@ if Module.const_defined? :WillPaginate
4
4
  require 'will_paginate/array'
5
5
  end
6
6
 
7
+ def MagicGrid::compact_hash(hash)
8
+ hash.select {|_,v| v }
9
+ end
10
+
7
11
  module MagicGrid
8
12
  module Helpers
9
13
  def normalize_magic(collection, columns = [], options = {})
10
- if collection.is_a? MagicGrid::Definition
11
- collection
12
- elsif columns.is_a? MagicGrid::Definition
13
- columns
14
- elsif options.is_a? MagicGrid::Definition
15
- options
16
- else
17
- MagicGrid::Definition.new(columns, collection, controller, options)
18
- end
19
- end
20
-
21
- def magic_collection(collection, cols, opts = {})
22
- normalize_magic(collection, cols, opts).collection
14
+ args_enum = [collection, columns, options].to_enum
15
+ given_grid = args_enum.find {|e| e.is_a? MagicGrid::Definition }
16
+ given_grid || MagicGrid::Definition.new(columns, collection, controller, options)
23
17
  end
24
18
 
25
19
  def magic_grid(collection = nil, cols = nil, opts = {}, &block)
26
20
  grid = normalize_magic(collection, cols, opts)
27
21
  base_params = grid.base_params
28
22
  data = {
29
- :searcher => grid.options[:searcher],
23
+ :searcher => grid.searcher,
30
24
  :current => controller.request.fullpath,
31
25
  :live_search => grid.options[:live_search],
32
26
  :listeners => (grid.options[:listeners] unless grid.options[:listeners].empty?),
@@ -38,7 +32,7 @@ module MagicGrid
38
32
  content_tag('table',
39
33
  :class => classes.join(' '),
40
34
  :id => grid.magic_id,
41
- :data => data.select {|_,v| v }
35
+ :data => MagicGrid.compact_hash(data)
42
36
  ) do
43
37
  table = content_tag('thead', :data => {:params => base_params}
44
38
  ) do
@@ -48,7 +42,7 @@ module MagicGrid
48
42
  :id => (grid.magic_id.to_s + "_spinner"),
49
43
  :class => "magic_grid_spinner"
50
44
  )
51
- if grid.options[:needs_searcher]
45
+ if grid.needs_searcher?
52
46
  thead << content_tag('tr') do
53
47
  content_tag('td', :class => 'searcher full-width ui-widget-header',
54
48
  :colspan => grid.columns.count) do
@@ -62,18 +56,10 @@ module MagicGrid
62
56
  end
63
57
  end
64
58
  if grid.options[:per_page] and grid.options[:top_pager]
65
- thead << content_tag('tr') do
66
- content_tag('td', :class => 'full-width ui-widget-header',
67
- :colspan => grid.columns.count) do
68
- pager = magic_paginate(grid.collection,
69
- :param_name => grid.param_key(:page),
70
- :params => base_params
71
- )
72
- unless has_spinner
73
- has_spinner = true
74
- pager << spinner
75
- end
76
- pager
59
+ thead << magic_pager(grid, base_params) do
60
+ unless has_spinner
61
+ has_spinner = true
62
+ spinner
77
63
  end
78
64
  end
79
65
  end
@@ -96,15 +82,7 @@ module MagicGrid
96
82
  table << content_tag('tfoot') do
97
83
  tfoot = ''.html_safe
98
84
  if grid.options[:per_page] and grid.options[:bottom_pager]
99
- tfoot << content_tag('tr') do
100
- content_tag('td', :class => 'full-width ui-widget-header',
101
- :colspan => grid.columns.count) do
102
- magic_paginate(grid.collection,
103
- :param_name => grid.param_key(:page),
104
- :params => base_params
105
- )
106
- end
107
- end
85
+ tfoot << magic_pager(grid, base_params)
108
86
  end
109
87
  if tfoot.empty? and not grid.options[:empty_footer]
110
88
  tfoot = content_tag 'tr' do
@@ -231,9 +209,9 @@ module MagicGrid
231
209
  :min_length => grid.options[:min_search_length],
232
210
  :current => grid.options[:current_search] || "",
233
211
  }
234
- searcher = label_tag(grid.options[:searcher].to_sym,
235
- grid.options[:searcher_label])
236
- searcher << search_field_tag(grid.options[:searcher].to_sym,
212
+ searcher = label_tag(grid.searcher.to_sym,
213
+ grid.options[:searcher_label])
214
+ searcher << search_field_tag(grid.searcher.to_sym,
237
215
  grid.param(:q),
238
216
  :placeholder => grid.options[:searcher_tooltip],
239
217
  :size => grid.options[:searcher_size],
@@ -261,6 +239,20 @@ module MagicGrid
261
239
  end
262
240
  end
263
241
 
242
+ def magic_pager(grid, base_params, &block)
243
+ content_tag('tr') do
244
+ content_tag('td', :class => 'full-width ui-widget-header magic-pager',
245
+ :colspan => grid.columns.count) do
246
+ pager = magic_paginate(grid.collection,
247
+ :param_name => grid.param_key(:page),
248
+ :params => base_params
249
+ )
250
+ pager << capture(&block) if block_given?
251
+ pager
252
+ end
253
+ end
254
+ end
255
+
264
256
  ::ActionView::Base.send :include, self
265
257
  end
266
258
  end
@@ -0,0 +1,8 @@
1
+ module MagicGrid
2
+ def self.logger=(logger)
3
+ @logger = logger
4
+ end
5
+ def self.logger
6
+ @logger ||= Logger.new(STDOUT)
7
+ end
8
+ end
@@ -1,3 +1,3 @@
1
1
  module MagicGrid
2
- VERSION = "0.10.4"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'magic_grid/collection'
3
+
4
+ describe MagicGrid::Collection do
5
+
6
+ context "via [] class method" do
7
+ context "when given a MagicGrid::Collection" do
8
+ let(:actual_collection) { [1,2,3,4] }
9
+ let(:magic_collection) { MagicGrid::Collection.new(actual_collection, :original_grid) }
10
+ subject { MagicGrid::Collection[magic_collection, :new_grid] }
11
+ its(:collection) { should eq(actual_collection) }
12
+ its(:grid) { should eq(:new_grid) }
13
+ end
14
+ context "when given a basic collection" do
15
+ let(:actual_collection) { [1,2,3,4] }
16
+ subject { MagicGrid::Collection[actual_collection, :original_grid] }
17
+ its(:collection) { should eq(actual_collection) }
18
+ its(:grid) { should eq(:original_grid) }
19
+ end
20
+ end
21
+
22
+ context "when based on an array" do
23
+ let(:collection) { [1,2,3,4] }
24
+ subject { MagicGrid::Collection.new(collection, nil) }
25
+ its(:collection) { should eq(collection) }
26
+ its(:grid) { should be_nil }
27
+ end
28
+
29
+ context "when based on something sortable" do
30
+ data = [1,5,3,2,56,7]
31
+ let(:sortable_collection) {
32
+ data.tap do |d|
33
+ d.stub(:order) { d }
34
+ end
35
+ }
36
+ it "should send #order when sorted" do
37
+ ordered = [1,2,3,4,5]
38
+ collection = MagicGrid::Collection.new(sortable_collection, nil)
39
+ sortable_collection.should_receive(:order) { ordered }
40
+ collection.apply_sort("col", "order")
41
+ collection.collection.should == ordered
42
+ end
43
+ end
44
+ end
@@ -67,6 +67,13 @@ describe MagicGrid::Definition do
67
67
  its(:columns) { should == column_list }
68
68
  end
69
69
 
70
+ context "when given a MagicGrid::Collection" do
71
+ actual_collection = [1,2,3]
72
+ let(:collection) { MagicGrid::Collection.new(actual_collection, nil) }
73
+ subject { MagicGrid::Definition.new(column_list, collection, controller) }
74
+ its(:collection) { should eq(actual_collection) }
75
+ end
76
+
70
77
  context "when given a large collection and some options" do
71
78
  let(:controller) {
72
79
  controller = double()
@@ -87,6 +94,110 @@ describe MagicGrid::Definition do
87
94
  subject.param_key(:hunkydory).should == :grid_hunkydory
88
95
  subject.param(:page).should == 2
89
96
  end
97
+ end
98
+
99
+ context "sorting" do
100
+ data = [1,56,7,21,1]
101
+ let(:controller) {
102
+ controller = double()
103
+ controller.stub(:params) { HashWithIndifferentAccess.new({grid_order: 1}) }
104
+ controller
105
+ }
106
+ let(:collection) { data }
107
+ it "should sort collection using #order" do
108
+ collection.should_receive(:order).with("foo DESC") { data.sort.reverse }
109
+ grid = MagicGrid::Definition.new([{:sql => "foo"}], collection, controller, id: :grid)
110
+
111
+ grid.collection.should == data.sort.reverse
112
+ end
113
+ pending "test #order_sql directly"
114
+ end
115
+
116
+ context "filtering with #where" do
117
+ data = [1,56,7,21,1]
118
+ let(:controller) {
119
+ controller = double.tap do |c|
120
+ c.stub(:params) { HashWithIndifferentAccess.new({f1: 1}) }
121
+ end
122
+ }
123
+ let(:collection) {
124
+ data.tap do |d|
125
+ d.stub(:where) do |h|
126
+ d.select { |d| d < 10 }
127
+ end
128
+ end
129
+ }
130
+ subject { MagicGrid::Definition.new([{:sql => "foo"}],
131
+ collection,
132
+ controller,
133
+ id: :grid, listeners: {f1: :f1}) }
134
+ its(:collection) { should == [1, 7, 1] }
135
+ end
136
+
137
+ context "filtering with a callback" do
138
+ data = [1,56,7,21,1]
139
+ filter = Proc.new do |c|
140
+ c.select { |i| i > 10 }
141
+ end
142
+ let(:controller) {
143
+ controller = double.tap do |c|
144
+ c.stub(:params) { HashWithIndifferentAccess.new({f1: 1}) }
145
+ end
146
+ }
147
+ let(:collection) {
148
+ data
149
+ }
150
+ subject { MagicGrid::Definition.new([{:sql => "foo"}],
151
+ collection,
152
+ controller,
153
+ id: :grid, listener_handler: filter) }
154
+ its(:collection) { should == [56, 21] }
155
+ end
156
+
157
+ pending "test listening on a dumb collection"
158
+
159
+ context "post_filtering with a callable post_filter" do
160
+ data = [1,56,7,21,1]
161
+ filter = Proc.new do |c|
162
+ c.select { |i| i > 10 }
163
+ end
164
+ let(:controller) {
165
+ controller = double.tap do |c|
166
+ c.stub(:params) { HashWithIndifferentAccess.new({f1: 1}) }
167
+ end
168
+ }
169
+ let(:collection) {
170
+ data
171
+ }
172
+ subject { MagicGrid::Definition.new([{:sql => "foo"}],
173
+ collection,
174
+ controller,
175
+ id: :grid, post_filter: filter) }
176
+ its(:collection) { should == [56, 21] }
177
+ end
90
178
 
179
+ context "post_filtering with a collection post_filter" do
180
+ data = [1,56,7,21,1]
181
+ filter = Proc.new do |c|
182
+ c.select { |i| i > 10 }
183
+ end
184
+ let(:controller) {
185
+ controller = double.tap do |c|
186
+ c.stub(:params) { HashWithIndifferentAccess.new({f1: 1}) }
187
+ end
188
+ }
189
+ let(:collection) {
190
+ data.tap do |d|
191
+ d.stub(:post_filter) do |h|
192
+ d.select { |d| d > 10 }
193
+ end
194
+ end
195
+ }
196
+ subject { MagicGrid::Definition.new([{:sql => "foo"}],
197
+ collection,
198
+ controller,
199
+ id: :grid, collection_post_filter?: true) }
200
+ its(:collection) { should == [56, 21] }
91
201
  end
202
+
92
203
  end
data/spec/helpers_spec.rb CHANGED
@@ -3,23 +3,44 @@ require 'magic_grid/helpers'
3
3
  require 'action_controller'
4
4
  require "active_support/core_ext"
5
5
 
6
+ def make_controller
7
+ request = double.tap { |r|
8
+ r.stub(:fullpath, "/foo?page=bar")
9
+ }
10
+ double.tap { |v|
11
+ v.stub(:render)
12
+ v.stub(:params) { {} }
13
+ v.stub(:request) { request }
14
+ }
15
+ end
16
+
17
+ def fake_connection
18
+ double(:connection).tap do |c|
19
+ c.stub(:quote_column_name) { |col| col.to_s }
20
+ end
21
+ end
22
+
23
+ def fake_active_record_collection(table_name = 'some_table')
24
+ (1..1000).to_a.tap do |c|
25
+ c.stub(:connection => fake_connection)
26
+ c.stub(:table_name => table_name)
27
+ c.stub(:where) { c }
28
+ end
29
+ end
30
+
31
+
6
32
  describe MagicGrid::Helpers do
7
33
 
8
34
  # Let's use the helpers the way they're meant to be used!
9
35
  include MagicGrid::Helpers
10
36
 
11
37
  let(:empty_collection) { [] }
38
+
12
39
  let(:column_list) { [:name, :description] }
13
- let(:controller) {
14
- request = double.tap{ |r|
15
- r.stub(:fullpath, "/foo?page=bar")
16
- }
17
- double.tap { |v|
18
- v.stub(:render) { nil }
19
- v.stub(:params) { {} }
20
- v.stub(:request) { request }
21
- }
22
- }
40
+
41
+ let(:controller) { make_controller }
42
+
43
+ # Kaminari uses view_renderer instead of controller
23
44
  let(:view_renderer) { controller }
24
45
 
25
46
  describe "#normalize_magic" do
@@ -28,40 +49,32 @@ describe MagicGrid::Helpers do
28
49
  expect(normalize_magic([])).to be_a(MagicGrid::Definition)
29
50
  end
30
51
 
31
- it "should give back the MagicGrid::Definition given, if given one" do
52
+ it "should give back the MagicGrid::Definition given, if given as any argument" do
32
53
  definition = normalize_magic([])
33
- expect(normalize_magic(definition)).to be(definition)
54
+ expect(normalize_magic( definition )).to be(definition)
55
+ expect(normalize_magic( nil, definition )).to be(definition)
56
+ expect(normalize_magic( nil, nil, definition )).to be(definition)
34
57
  end
35
58
  end
36
59
 
37
- describe "#magic_collection" do
38
- pending "should probably be removed, it's not really used"
39
-
40
- it "should give back a collection like the one given" do
41
- my_empty_collection = empty_collection
42
- expect(magic_collection(my_empty_collection, column_list)).to eq(my_empty_collection)
43
- end
44
-
45
- end
46
-
47
60
  describe "#magic_grid" do
48
61
  pending "DOES WAY TOO MUCH!!"
49
62
 
63
+ let(:emtpy_grid) { magic_grid empty_collection, column_list }
64
+
50
65
  it "should barf without any arguments" do
51
66
  expect { magic_grid }.to raise_error
52
67
  end
53
68
 
54
- let(:emtpy_grid) { magic_grid empty_collection, column_list }
55
-
56
69
  it "should render a table" do
57
70
  expect( emtpy_grid ).not_to be_empty
58
71
  expect( emtpy_grid ).to match(/<\/table>/)
59
72
  end
60
73
 
61
74
  context "when given an empty collection" do
62
- let(:empty_grid) { magic_grid(empty_collection, column_list) }
75
+ subject { magic_grid empty_collection, column_list }
63
76
  it "should indicate there is no data" do
64
- expect(empty_grid).to match(/"if-empty"/)
77
+ subject.should match(/"if-empty"/)
65
78
  end
66
79
  end
67
80
 
@@ -82,6 +95,163 @@ describe MagicGrid::Helpers do
82
95
  }
83
96
  it { should =~ /HOKY_POKY_ALAMO: 1/ }
84
97
  end
98
+
99
+ context "renders top and bottom pagers as told" do
100
+ large_collection = (1..1000).to_a
101
+
102
+ if Module.const_defined? :Kaminari
103
+ def render(*args)
104
+ "<nav class='pagination'><!-- paginate! --></nav>".html_safe
105
+ end
106
+ end
107
+
108
+ it "should render an actual pager" do
109
+ grid = magic_grid(large_collection, [:to_s])
110
+ if Module.const_defined? :WillPaginate
111
+ grid.should match_select("tfoot>tr>td.magic-pager>div.pagination", 1)
112
+ elsif Module.const_defined? :Kaminari
113
+ grid.should match_select("tfoot>tr>td.magic-pager>nav.pagination", 1)
114
+ else
115
+ grid.should match_select("tfoot>tr>td.magic-pager", /INSTALL/)
116
+ end
117
+ end
118
+ it "should render only a bottom pager by default" do
119
+ grid = magic_grid( large_collection, [:to_s] )
120
+ grid.should match_select("thead>tr>td.magic-pager", 0)
121
+ grid.should match_select("tfoot>tr>td.magic-pager", 1)
122
+ end
123
+ it "should render a top and bottom pager when told" do
124
+ grid = magic_grid( large_collection, [:to_s], top_pager: true )
125
+ grid.should match_select("thead>tr>td.magic-pager", 1)
126
+ grid.should match_select("tfoot>tr>td.magic-pager", 1)
127
+ end
128
+ it "should render only a top pager when told" do
129
+ grid = magic_grid( large_collection, [:to_s], top_pager: true, bottom_pager: false )
130
+ grid.should match_select("thead>tr>td.magic-pager", 1)
131
+ grid.should match_select("tfoot>tr>td.magic-pager", 0)
132
+ end
133
+ end
134
+
135
+ context "searching" do
136
+ let(:searchabe_collection) {
137
+ collection = [].tap do |c|
138
+ c.stub(:search) { collection }
139
+ end
140
+ }
141
+ it "should render a search bar when asked" do
142
+ grid = magic_grid(searchabe_collection, column_list, :searchable => [:some_col])
143
+ grid.should match_select('input[type=search]')
144
+ end
145
+
146
+ context "when a search query is given" do
147
+ let(:search_param) { 'foobar' }
148
+ let(:controller) {
149
+ make_controller.tap { |c|
150
+ c.stub(:params) { {:grid_id_q => search_param} }
151
+ }
152
+ }
153
+ it "should search a searchable collection when there are search params" do
154
+ collection = (1..1000).to_a
155
+ collection.should_receive(:search).with(search_param) { collection }
156
+ grid = magic_grid(collection, column_list, :id => "grid_id", :searchable => [:some_col])
157
+ grid.should match_select('input[type=search]')
158
+ end
159
+
160
+ context "when the collection responds to #where" do
161
+ it "should call where when there are search params" do
162
+ search_col = :some_col
163
+ table_name = "tbl"
164
+ search_sql = "tbl.some_col"
165
+
166
+ collection = fake_active_record_collection(table_name)
167
+ collection.should_receive(:where).
168
+ with("#{search_sql} LIKE :search", {:search=>"%#{search_param}%"})
169
+
170
+ grid = magic_grid(collection, column_list, :id => "grid_id", :searchable => [search_col])
171
+ end
172
+
173
+ it "should use custom sql from column for call to where when given" do
174
+ search_col = :some_col
175
+ search_sql = "sql that doesn't necessarily match column name"
176
+ table_name = "table name not used in query"
177
+
178
+ collection = fake_active_record_collection(table_name)
179
+ collection.should_receive(:where).
180
+ with("#{search_sql} LIKE :search", {:search=>"%#{search_param}%"})
181
+
182
+ magic_grid(collection,
183
+ [ :name,
184
+ :description,
185
+ {:col => search_col, :sql => search_sql}
186
+ ],
187
+ :id => "grid_id", :searchable => [search_col])
188
+ end
189
+
190
+ it "should use column number to look up search column" do
191
+ search_col = :some_col
192
+ search_sql = "sql that doesn't necessarily match column name"
193
+ table_name = "table name not used in query"
194
+
195
+ collection = fake_active_record_collection(table_name)
196
+ collection.should_receive(:where).
197
+ with("#{search_sql} LIKE :search", {:search=>"%#{search_param}%"})
198
+
199
+ magic_grid(collection,
200
+ [ :name,
201
+ :description,
202
+ {:col => search_col, :sql => search_sql}
203
+ ],
204
+ :id => "grid_id", :searchable => [2])
205
+ end
206
+
207
+ it "should use custom sql for call to where when given" do
208
+ search_col = :some_col
209
+ custom_search_col = "some custom search column"
210
+ search_sql = custom_search_col
211
+ table_name = "table name not used in query"
212
+
213
+ collection = fake_active_record_collection(table_name)
214
+ collection.should_receive(:where).
215
+ with("#{search_sql} LIKE :search", {:search=>"%#{search_param}%"})
216
+
217
+ magic_grid(collection,
218
+ [ :name,
219
+ :description,
220
+ {:col => search_col, :sql => search_sql}
221
+ ],
222
+ :id => "grid_id", :searchable => [custom_search_col])
223
+ end
224
+
225
+ it "should fail when given bad searchable columns" do
226
+ collection = fake_active_record_collection()
227
+ collection.should_not_receive(:where)
228
+
229
+ expect {
230
+ magic_grid(collection,
231
+ [ :name, :description],
232
+ :id => "grid_id", :searchable => [nil])
233
+ }.to raise_error
234
+ end
235
+
236
+ it "should not fail if #where fails" do
237
+ search_col = :some_col
238
+ table_name = "tbl"
239
+ search_sql = "tbl.some_col"
240
+
241
+ collection = fake_active_record_collection(table_name)
242
+ magic_collection = MagicGrid::Collection.new(collection, nil)
243
+ collection.should_receive(:where).and_raise("some failure")
244
+ MagicGrid.logger = double.tap do |l|
245
+ l.should_receive(:debug).at_least(:once)
246
+ end
247
+
248
+ expect {
249
+ magic_grid(collection, column_list, :id => "grid_id", :searchable => [search_col])
250
+ }.to_not raise_error
251
+ end
252
+ end
253
+ end
254
+ end
85
255
  end
86
256
 
87
257
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'magic_grid/logger'
3
+
4
+ describe MagicGrid do
5
+ it "should user the specified logger" do
6
+ logger = double.tap do |l|
7
+ l.should_receive(:debug)
8
+ l.should_receive(:warn)
9
+ l.should_receive(:error)
10
+ end
11
+ MagicGrid.logger = logger
12
+ MagicGrid.logger.warn "Something is afoot"
13
+ MagicGrid.logger.error "Something is really wrong"
14
+ MagicGrid.logger.debug "Something is foo"
15
+ end
16
+ end
data/spec/spec_helper.rb CHANGED
@@ -9,13 +9,16 @@ unless ENV['TRAVIS']
9
9
  end
10
10
  end
11
11
 
12
+ require 'magic_grid/logger'
12
13
  require 'action_view'
13
14
  require 'rails'
15
+ require 'test/unit'
16
+ require 'action_controller'
14
17
 
15
18
  begin
16
19
  require 'will_paginate'
17
20
  require 'will_paginate/array'
18
- require 'will_paginate/view_helpers'
21
+ require 'will_paginate/view_helpers/action_view'
19
22
  puts "Testing with WillPaginate"
20
23
  rescue LoadError
21
24
  puts "skipping WillPaginate"
@@ -31,28 +34,33 @@ end
31
34
 
32
35
  Rails.backtrace_cleaner.remove_silencers!
33
36
 
34
- # I has a sad :-(
35
- module Rails
36
- def logger.debug(*ignore) end
37
+ class NullObject
38
+ def method_missing(*args, &block) self; end
39
+ def nil?; true; end
37
40
  end
38
41
 
39
42
  module ActionFaker
40
- def output_buffer=(o)
41
- @output_buffer = o
43
+ attr_accessor :output_buffer
44
+ def url_for(*args)
45
+ "fake_url(#{args.inspect})"
42
46
  end
43
- def output_buffer()
44
- @output_buffer
45
- end
46
- def url_for
47
- "fake_url"
47
+ end
48
+
49
+ class TextSelector
50
+ include ActionDispatch::Assertions::SelectorAssertions
51
+ include Test::Unit::Assertions
52
+ def initialize(text)
53
+ @selected = HTML::Document.new(text).root.children
48
54
  end
49
- def controller
50
- stub_controller = ActionController::Base.new
51
- def stub_controller.params(*ignored) {} end
52
- stub_controller
55
+ end
56
+
57
+ RSpec::Matchers.define :match_select do |*expected|
58
+ match do |actual|
59
+ TextSelector.new(actual).assert_select(*expected)
53
60
  end
54
61
  end
55
62
 
63
+
56
64
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
57
65
  RSpec.configure do |config|
58
66
  config.treat_symbols_as_metadata_keys_with_true_values = true
@@ -60,10 +68,14 @@ RSpec.configure do |config|
60
68
  config.filter_run :focus
61
69
 
62
70
  config.include ActionView::Helpers
63
- config.include WillPaginate::ViewHelpers if Module.const_defined? :WillPaginate
71
+ config.include WillPaginate::ActionView if Module.const_defined? :WillPaginate
64
72
  config.include Kaminari::ActionViewExtension if Module.const_defined? :Kaminari
65
73
  config.include ActionFaker
66
74
 
75
+ config.before do
76
+ MagicGrid.logger = NullObject.new
77
+ end
78
+
67
79
  # Run specs in random order to surface order dependencies. If you find an
68
80
  # order dependency and want to debug it, you can fix the order by providing
69
81
  # the seed, which is printed after each run.
@@ -11,7 +11,7 @@ class SpiderTest < ActionDispatch::IntegrationTest
11
11
  def test_users
12
12
  t = tarantula_crawler(self)
13
13
  #t.handlers << Relevance::Tarantula::TidyHandler.new
14
- t.crawl_timeout = 15.seconds
14
+ t.crawl_timeout = 5.seconds
15
15
  t.skip_uri_patterns += @@to_skip
16
16
  t.crawl '/users'
17
17
  end
@@ -19,7 +19,7 @@ class SpiderTest < ActionDispatch::IntegrationTest
19
19
  def test_posts
20
20
  t = tarantula_crawler(self)
21
21
  #t.handlers << Relevance::Tarantula::TidyHandler.new
22
- t.crawl_timeout = 15.seconds
22
+ t.crawl_timeout = 5.seconds
23
23
  t.skip_uri_patterns += @@to_skip
24
24
  t.crawl '/posts'
25
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magic_grid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.4
4
+ version: 0.11.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-12 00:00:00.000000000 Z
12
+ date: 2012-10-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -106,14 +106,17 @@ files:
106
106
  - lib/assets/javascripts/magic_grid.js
107
107
  - lib/tasks/magic_grid_tasks.rake
108
108
  - lib/magic_grid/version.rb
109
- - lib/magic_grid/railtie.rb
110
109
  - lib/magic_grid/helpers.rb
110
+ - lib/magic_grid/collection.rb
111
111
  - lib/magic_grid/engine.rb
112
+ - lib/magic_grid/logger.rb
112
113
  - lib/magic_grid/definition.rb
113
114
  - lib/locales/en.yml
114
115
  - MIT-LICENSE
115
116
  - Rakefile
116
117
  - README.md
118
+ - spec/collection_spec.rb
119
+ - spec/logger_spec.rb
117
120
  - spec/spec_helper.rb
118
121
  - spec/helpers_spec.rb
119
122
  - spec/definition_spec.rb
@@ -208,7 +211,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
208
211
  version: '0'
209
212
  segments:
210
213
  - 0
211
- hash: 591195015863992118
214
+ hash: 3180457643805959296
212
215
  required_rubygems_version: !ruby/object:Gem::Requirement
213
216
  none: false
214
217
  requirements:
@@ -217,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
220
  version: '0'
218
221
  segments:
219
222
  - 0
220
- hash: 591195015863992118
223
+ hash: 3180457643805959296
221
224
  requirements: []
222
225
  rubyforge_project:
223
226
  rubygems_version: 1.8.23
@@ -225,6 +228,8 @@ signing_key:
225
228
  specification_version: 3
226
229
  summary: Easy collection display grid with column sorting and pagination
227
230
  test_files:
231
+ - spec/collection_spec.rb
232
+ - spec/logger_spec.rb
228
233
  - spec/spec_helper.rb
229
234
  - spec/helpers_spec.rb
230
235
  - spec/definition_spec.rb
@@ -1,9 +0,0 @@
1
- module MagicGrid
2
- class Railtie < Rails::Railtie
3
- # Once in production, on every page view in development
4
- config.to_prepare do
5
- # no caching based on path like require does
6
- load 'magic_grid/helpers.rb'
7
- end
8
- end
9
- end