magic_grid 0.10.4 → 0.11.0

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