paginative 0.2.3 → 0.3.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.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/lib/paginative/models/model_extension.rb +92 -15
- data/lib/paginative/models/ordering_helpers.rb +4 -11
- data/lib/paginative/version.rb +1 -1
- data/spec/dummy/app/models/joint_model.rb +2 -0
- data/spec/dummy/app/models/test_model.rb +10 -0
- data/spec/dummy/db/migrate/20150922072155_create_joint_models.rb +10 -0
- data/spec/dummy/db/schema.rb +14 -5
- data/spec/dummy/spec/factories/joint_models.rb +6 -0
- data/spec/factories/paginative_joint_models.rb +7 -0
- data/spec/models/paginative/test_model_spec.rb +86 -5
- data/spec/spec_helper.rb +1 -0
- metadata +38 -9
- data/app/models/paginative/test_model.rb +0 -5
- data/db/migrate/20140415060518_create_paginative_test_models.rb +0 -11
- data/db/migrate/20140416020706_add_address_to_test_models.rb +0 -5
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +0 -15471
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b88a52a9b7c22d3a811c3e011020e3b7ea1c624a
|
4
|
+
data.tar.gz: c5b8c8e2f43df8fed6cc57502afc717cad2944eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0f7ac4dcf60b1741760d3ba714ed7d9ae2931c46a8b843e89655fbd359ed889329a9818e7026aa7d575c15a0931e95f618c23f8e8ba411fae21ecb56616eaa0
|
7
|
+
data.tar.gz: c0a047e197c29d41642368da7cd4dd12de36db57e412c8d5969bb5278277dae2b7f713a590268c87b405cc85ef66660ca4fd1d3e651dc6ee615ddc9370940575
|
data/Rakefile
CHANGED
@@ -5,6 +5,9 @@ module Paginative
|
|
5
5
|
included do
|
6
6
|
include Paginative::OrderingHelpers
|
7
7
|
|
8
|
+
mattr_accessor :paginative_fields
|
9
|
+
@@paginative_fields = {}
|
10
|
+
|
8
11
|
def self.by_distance_from(latitude, longitude, distance=0, limit=25)
|
9
12
|
return [] unless latitude.present? && longitude.present?
|
10
13
|
distance_sql = send(:distance_sql, latitude.to_f, longitude.to_f, {:units => :km, select_bearing: false})
|
@@ -23,24 +26,98 @@ module Paginative
|
|
23
26
|
|
24
27
|
def self.with_field_from(field="", value="", limit=25, order="asc")
|
25
28
|
order ||= "asc"
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
|
30
|
+
# Wrap and flatten whatever comes in, if it's a single value, we end up with an array.
|
31
|
+
# If it's an array, stays an array.
|
32
|
+
fields = Array.wrap(field).flatten
|
33
|
+
values = Array.wrap(value).flatten
|
34
|
+
zipped = fields.zip(values)
|
35
|
+
fields, values = prune_fields(zipped).transpose
|
36
|
+
|
37
|
+
q = self.all
|
38
|
+
if fields.present? && fields.any?
|
39
|
+
return raise "Wrong number of values. Expected 2, got #{values.try(:length)}. You must pass a value for each field that you are sorting by" unless values.length <= 2 && values.length == fields.length
|
40
|
+
|
41
|
+
mapped_fields = map_fields(fields)
|
42
|
+
q = q.order(sanitized_ordering(self.table_name, mapped_fields, order))
|
43
|
+
|
44
|
+
mapped_fields.each_with_index do |field, idx|
|
45
|
+
if idx == 0
|
46
|
+
value = values[idx]
|
47
|
+
operator = sort_operator(idx, mapped_fields.count, order)
|
48
|
+
|
49
|
+
q = q.where("#{field} #{operator} ?", value)
|
50
|
+
else
|
51
|
+
previous_field = mapped_fields[idx - 1]
|
52
|
+
previous_value = values[idx - 1]
|
53
|
+
value = values[idx]
|
54
|
+
operator = sort_operator(idx, mapped_fields.count, order)
|
55
|
+
|
56
|
+
q = q.where("#{previous_field} != ? OR #{field} #{operator} ?", previous_value, value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return q.limit(limit)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Steps through the provided paginated fields, zipped with their values, and removes those not
|
67
|
+
# in the `paginative_fields` hash as specified with `allow_paginative_on`.
|
68
|
+
def self.prune_fields(zipped)
|
69
|
+
zipped.select{ |f, v| self.paginative_fields.has_key? f.to_sym }.tap do |pruned|
|
70
|
+
unless pruned.nil?
|
71
|
+
items = zipped.map(&:first) - pruned.map(&:first)
|
72
|
+
Rails.logger.warn "Paginative ignored unpermitted field: #{items}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Takes the pruned fields as an array, and returns the mapped versions.
|
78
|
+
def self.map_fields(fields)
|
79
|
+
fields.map{ |f| self.paginative_fields[f.to_sym] }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the appropriate order given sort direction and current field in the collection.
|
83
|
+
# We don't want to use inclusive paging if we are at the last field being paginated, so either lt or gt is used.
|
84
|
+
def self.sort_operator(index, count, direction)
|
85
|
+
if direction.try(:downcase) == "desc"
|
86
|
+
index < (count - 1) ? '<=' : '<'
|
87
|
+
else
|
88
|
+
index < (count - 1) ? '>=' : '>'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module ClassMethods
|
94
|
+
# Sets the paginative fields set of the class to the specified columns.
|
95
|
+
def allow_paginative_on(*mappings)
|
96
|
+
self.paginative_fields = process_fields(mappings)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Process specified mappings to either scope to the table name of the current class
|
102
|
+
# or to use the mappings provided.
|
103
|
+
def process_fields(mappings)
|
104
|
+
result = {}
|
105
|
+
|
106
|
+
mappings.each do |mapping|
|
107
|
+
if mapping.is_a?(Hash)
|
108
|
+
result.merge!(mapping)
|
38
109
|
else
|
39
|
-
|
40
|
-
return self.order(sanitized_ordering(self.table_name, field, order)).where("#{self.table_name}.#{field} < ?", value).limit(limit) if order.try(:downcase) == "desc"
|
41
|
-
self.order(sanitized_ordering(self.table_name, field, order)).where("#{self.table_name}.#{field} > ?", value).limit(limit)
|
110
|
+
result[mapping] = self_map(mapping)
|
42
111
|
end
|
43
112
|
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns a string scoping the specified field to the current class' table.
|
118
|
+
def self_map(field)
|
119
|
+
"#{self.table_name}.#{field}"
|
44
120
|
end
|
45
121
|
end
|
46
122
|
end
|
123
|
+
end
|
@@ -3,20 +3,13 @@ module Paginative
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
def self.sanitized_ordering(table_name,
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
"#{table_name}.#{sanitize_column(field[0])} #{sanitize_column_direction(order)}, #{table_name}.#{sanitize_column(field[1])} #{sanitize_column_direction(order)}"
|
11
|
-
else
|
12
|
-
"#{table_name}.#{sanitize_column(field)} #{sanitize_column_direction(order)}"
|
13
|
-
end
|
6
|
+
def self.sanitized_ordering(table_name, fields, order)
|
7
|
+
fields.map do |field|
|
8
|
+
"#{field} #{sanitize_column_direction(order)}"
|
9
|
+
end.join(', ')
|
14
10
|
end
|
15
11
|
|
16
12
|
private
|
17
|
-
def self.sanitize_column(column)
|
18
|
-
self.column_names.include?(column) ? column : "created_at"
|
19
|
-
end
|
20
13
|
|
21
14
|
def self.sanitize_column_direction(direction)
|
22
15
|
direction = direction.upcase
|
data/lib/paginative/version.rb
CHANGED
@@ -2,4 +2,14 @@ class TestModel < ActiveRecord::Base
|
|
2
2
|
include Paginative::ModelExtension
|
3
3
|
|
4
4
|
reverse_geocoded_by :latitude, :longitude
|
5
|
+
|
6
|
+
# Associations
|
7
|
+
has_many :joint_models
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def joint
|
11
|
+
joins(:joint_models).select('test_models.*, joint_models.created_at')
|
12
|
+
.order('joint_models.created_at DESC')
|
13
|
+
end
|
14
|
+
end
|
5
15
|
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -9,17 +9,26 @@
|
|
9
9
|
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
10
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
11
|
#
|
12
|
-
# It's strongly recommended
|
12
|
+
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(:
|
14
|
+
ActiveRecord::Schema.define(version: 20150922072155) do
|
15
15
|
|
16
|
-
create_table "
|
16
|
+
create_table "joint_models", force: :cascade do |t|
|
17
|
+
t.string "name"
|
18
|
+
t.integer "test_model_id"
|
19
|
+
t.datetime "created_at", null: false
|
20
|
+
t.datetime "updated_at", null: false
|
21
|
+
end
|
22
|
+
|
23
|
+
add_index "joint_models", ["test_model_id"], name: "index_joint_models_on_test_model_id"
|
24
|
+
|
25
|
+
create_table "test_models", force: :cascade do |t|
|
17
26
|
t.string "name"
|
18
27
|
t.string "address"
|
19
28
|
t.float "latitude"
|
20
29
|
t.float "longitude"
|
21
|
-
t.datetime "created_at"
|
22
|
-
t.datetime "updated_at"
|
30
|
+
t.datetime "created_at"
|
31
|
+
t.datetime "updated_at"
|
23
32
|
end
|
24
33
|
|
25
34
|
end
|
@@ -4,10 +4,7 @@ describe TestModel do
|
|
4
4
|
|
5
5
|
before :each do
|
6
6
|
TestModel.class_eval do
|
7
|
-
|
8
|
-
|
9
|
-
reverse_geocoded_by :latitude, :longitude
|
10
|
-
after_validation :reverse_geocode # auto-fetch coordinates
|
7
|
+
allow_paginative_on :id, :latitude, :longitude, :name, :address, :created_at
|
11
8
|
end
|
12
9
|
end
|
13
10
|
|
@@ -53,7 +50,6 @@ describe TestModel do
|
|
53
50
|
end
|
54
51
|
|
55
52
|
context "by name" do
|
56
|
-
|
57
53
|
it "is valid" do
|
58
54
|
model = FactoryGirl.create(:test_model)
|
59
55
|
|
@@ -81,6 +77,15 @@ describe TestModel do
|
|
81
77
|
end
|
82
78
|
|
83
79
|
context "By distance" do
|
80
|
+
before :each do
|
81
|
+
TestModel.class_eval do
|
82
|
+
include Paginative::ModelExtension
|
83
|
+
|
84
|
+
allow_paginative_on :latitude, :longitude
|
85
|
+
reverse_geocoded_by :latitude, :longitude
|
86
|
+
after_validation :reverse_geocode
|
87
|
+
end
|
88
|
+
end
|
84
89
|
|
85
90
|
it "limits the results" do
|
86
91
|
models = FactoryGirl.create_list(:test_model, 30)
|
@@ -150,4 +155,80 @@ describe TestModel do
|
|
150
155
|
expect(TestModel.with_field_from(["latitude", "longitude"], [150, 12])).to eq [@third, @fourth]
|
151
156
|
end
|
152
157
|
end
|
158
|
+
|
159
|
+
context 'restricted fields' do
|
160
|
+
before do
|
161
|
+
TestModel.paginative_fields = { name: 'test_models.name' }
|
162
|
+
|
163
|
+
@first = FactoryGirl.create(:test_model, name: 'abc', address: 'abc')
|
164
|
+
@second = FactoryGirl.create(:test_model, name: 'abc', address: 'bcd')
|
165
|
+
@third = FactoryGirl.create(:test_model, name: 'abc', address: 'cde')
|
166
|
+
@fourth = FactoryGirl.create(:test_model, name: 'abc', address: 'def')
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'ignores unpermitted fields with a warning' do
|
170
|
+
expect(Rails.logger).to receive(:warn)
|
171
|
+
|
172
|
+
TestModel.with_field_from('address', 'bcd')
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'prunes the fields to those only permitted' do
|
176
|
+
expect(TestModel).to receive(:map_fields).with(['name']) { ['test_models.name'] }
|
177
|
+
TestModel.with_field_from(["name", "address"], ["abc", "bcd"])
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'returns the original scope and ordering, still limited' do
|
181
|
+
result = TestModel.with_field_from('address', 'bcd', 2)
|
182
|
+
expect(result).to eq [@first, @second]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe 'restricting fields' do
|
187
|
+
before do
|
188
|
+
TestModel.paginative_fields = {}
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'no fields' do
|
192
|
+
it 'defaults to no paginative fields' do
|
193
|
+
expect(TestModel.paginative_fields).to be_empty
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'self mapped columns' do
|
198
|
+
before do
|
199
|
+
TestModel.class_eval do
|
200
|
+
allow_paginative_on :created_at
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'sets the paginative fields to self mappings' do
|
205
|
+
expect(TestModel.paginative_fields).to eq({ created_at: 'test_models.created_at' })
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'join mapped columns' do
|
210
|
+
before do
|
211
|
+
TestModel.class_eval do
|
212
|
+
allow_paginative_on created_at: 'other_models.created_at'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'sets the paginative fields to the specified mapping' do
|
217
|
+
expect(TestModel.paginative_fields).to eq({ created_at: 'other_models.created_at' })
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe 'paginating joint fields' do
|
223
|
+
before do
|
224
|
+
TestModel.paginative_fields = { created_at: 'joint_models.created_at' }
|
225
|
+
|
226
|
+
@first = FactoryGirl.create(:test_model, name: 'abc', address: 'abc', joint_models: [FactoryGirl.build(:joint_model, created_at: Time.now)])
|
227
|
+
@second = FactoryGirl.create(:test_model, name: 'abc', address: 'bcd', joint_models: [FactoryGirl.build(:joint_model, created_at: 5.minutes.ago)])
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'can be paginated on the secondary column (strings)' do
|
231
|
+
expect(TestModel.joint.with_field_from('created_at', 3.minutes.ago, 24, 'DESC')).to eq [@second]
|
232
|
+
end
|
233
|
+
end
|
153
234
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paginative
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Isaac Norman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -136,6 +136,34 @@ dependencies:
|
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: 1.2.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: appraisal
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: pry
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
139
167
|
description: After spending a lot of time screwing around with orphaned objects and
|
140
168
|
every other problem that pagination causes, this is the solution
|
141
169
|
email:
|
@@ -151,11 +179,8 @@ files:
|
|
151
179
|
- app/assets/stylesheets/paginative/application.css
|
152
180
|
- app/controllers/paginative/application_controller.rb
|
153
181
|
- app/helpers/paginative/application_helper.rb
|
154
|
-
- app/models/paginative/test_model.rb
|
155
182
|
- app/views/layouts/paginative/application.html.erb
|
156
183
|
- config/routes.rb
|
157
|
-
- db/migrate/20140415060518_create_paginative_test_models.rb
|
158
|
-
- db/migrate/20140416020706_add_address_to_test_models.rb
|
159
184
|
- lib/paginative.rb
|
160
185
|
- lib/paginative/engine.rb
|
161
186
|
- lib/paginative/models/model_extension.rb
|
@@ -169,6 +194,7 @@ files:
|
|
169
194
|
- spec/dummy/app/assets/stylesheets/application.css
|
170
195
|
- spec/dummy/app/controllers/application_controller.rb
|
171
196
|
- spec/dummy/app/helpers/application_helper.rb
|
197
|
+
- spec/dummy/app/models/joint_model.rb
|
172
198
|
- spec/dummy/app/models/test_model.rb
|
173
199
|
- spec/dummy/app/views/layouts/application.html.erb
|
174
200
|
- spec/dummy/bin/bundle
|
@@ -193,13 +219,14 @@ files:
|
|
193
219
|
- spec/dummy/config/routes.rb
|
194
220
|
- spec/dummy/config/secrets.yml
|
195
221
|
- spec/dummy/db/migrate/20140416035443_create_test_models.rb
|
222
|
+
- spec/dummy/db/migrate/20150922072155_create_joint_models.rb
|
196
223
|
- spec/dummy/db/schema.rb
|
197
|
-
- spec/dummy/db/test.sqlite3
|
198
|
-
- spec/dummy/log/test.log
|
199
224
|
- spec/dummy/public/404.html
|
200
225
|
- spec/dummy/public/422.html
|
201
226
|
- spec/dummy/public/500.html
|
202
227
|
- spec/dummy/public/favicon.ico
|
228
|
+
- spec/dummy/spec/factories/joint_models.rb
|
229
|
+
- spec/factories/paginative_joint_models.rb
|
203
230
|
- spec/factories/paginative_test_models.rb
|
204
231
|
- spec/models/paginative/test_model_spec.rb
|
205
232
|
- spec/spec_helper.rb
|
@@ -232,6 +259,7 @@ test_files:
|
|
232
259
|
- spec/dummy/app/assets/stylesheets/application.css
|
233
260
|
- spec/dummy/app/controllers/application_controller.rb
|
234
261
|
- spec/dummy/app/helpers/application_helper.rb
|
262
|
+
- spec/dummy/app/models/joint_model.rb
|
235
263
|
- spec/dummy/app/models/test_model.rb
|
236
264
|
- spec/dummy/app/views/layouts/application.html.erb
|
237
265
|
- spec/dummy/bin/bundle
|
@@ -256,15 +284,16 @@ test_files:
|
|
256
284
|
- spec/dummy/config/secrets.yml
|
257
285
|
- spec/dummy/config.ru
|
258
286
|
- spec/dummy/db/migrate/20140416035443_create_test_models.rb
|
287
|
+
- spec/dummy/db/migrate/20150922072155_create_joint_models.rb
|
259
288
|
- spec/dummy/db/schema.rb
|
260
|
-
- spec/dummy/db/test.sqlite3
|
261
|
-
- spec/dummy/log/test.log
|
262
289
|
- spec/dummy/public/404.html
|
263
290
|
- spec/dummy/public/422.html
|
264
291
|
- spec/dummy/public/500.html
|
265
292
|
- spec/dummy/public/favicon.ico
|
266
293
|
- spec/dummy/Rakefile
|
267
294
|
- spec/dummy/README.rdoc
|
295
|
+
- spec/dummy/spec/factories/joint_models.rb
|
296
|
+
- spec/factories/paginative_joint_models.rb
|
268
297
|
- spec/factories/paginative_test_models.rb
|
269
298
|
- spec/models/paginative/test_model_spec.rb
|
270
299
|
- spec/spec_helper.rb
|