order_as_specified 0.1.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e011d7a057022124433414e9da18da5578a809d
4
- data.tar.gz: 331b7087e75d2610e695f86db3741db58ca46bc4
3
+ metadata.gz: aa46391db1a92ccc0095e732300f8ff9dff05219
4
+ data.tar.gz: 54dc39bd5f341a8a0245c25cfbbdb56a72590b4e
5
5
  SHA512:
6
- metadata.gz: 7b1371f79ccd95c02c121775077b0779089cd570605180b19722e471949ee14f640d4aaeaf0c14305670c15fa8801388f7c801dac8aa83e83f904697dcf6beef
7
- data.tar.gz: 9da69d269c2bdf3e01b9390d478234b0d07257aa7e8b06696211ec57748b513b0f2e2add820944f9ebb97cf85e9ce726cbb640e6dcc6c53198e0117063be9a80
6
+ metadata.gz: 92a5c23cd6dff0657c08cf71387b30fa8ce6ae4a1113c51f3aeb7ad8c6f75786f9449b49ebc1d8e445341bf65b3a9bd431febb00436c8d7c566a0c30247097e8
7
+ data.tar.gz: d0ce1a2a19c3f1d03d80d4c2ef940551423ea3fb986b30685d99966ad1b71cc91d94c60881a61c068af8b6927e78cbb4d909f8b9df5ffc42f2d88dd3542e108b
@@ -1,8 +1,11 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.2.1
4
+ before_script:
5
+ - psql -c 'create database order_as_specified_test;' -U postgres
4
6
  script: bundle exec rspec
5
7
  addons:
8
+ postgresql: "9.3"
6
9
  code_climate:
7
10
  repo_token: 781c439d68cbb928316deaec1c7136f98423e1db87238f99cbc95183de94df9e
8
11
  notifications:
data/README.md CHANGED
@@ -103,6 +103,23 @@ TestObject.order_as_specified(language: ["fr", "es"])
103
103
  ]>
104
104
  ```
105
105
 
106
+ In databases that support it (such as PostgreSQL), you can also use an option to
107
+ add a `DISTINCT ON` to your query when you would otherwise have duplicates:
108
+
109
+ ```ruby
110
+ TestObject.order_as_specified(distinct_on: true, language: ["fr", "en"])
111
+ => #<ActiveRecord::Relation [
112
+ #<TestObject id: 2, language: "fr">,
113
+ #<TestObject id: 3, language: "en">,
114
+ #<TestObject id: 4, language: "es">
115
+ ]>
116
+ ```
117
+
118
+ Note that if a `nil` value is passed in the ordering an error is raised, because
119
+ databases do not have good or consistent support for ordering with `NULL` values
120
+ in an arbitrary order, so we don't permit this behavior instead of allowing an
121
+ unexpected result.
122
+
106
123
  ## Documentation
107
124
 
108
125
  We have documentation on [RubyDoc](http://www.rubydoc.info/github/panorama-ed/order_as_specified/master).
@@ -1,4 +1,5 @@
1
1
  require "order_as_specified/version"
2
+ require "order_as_specified/error"
2
3
 
3
4
  # This module adds the ability to query an ActiveRecord class for results from
4
5
  # the database in an arbitrary order, without having to store anything extra
@@ -9,19 +10,28 @@ module OrderAsSpecified
9
10
  # @param hash [Hash] the ActiveRecord arguments hash
10
11
  # @return [ActiveRecord::Relation] the objects, ordered as specified
11
12
  def order_as_specified(hash)
13
+ distinct_on = hash.delete(:distinct_on)
12
14
  params = extract_params(hash)
13
15
 
14
16
  table = params[:table]
15
17
  attribute = params[:attribute]
16
18
 
17
19
  # We have to explicitly quote for now because SQL sanitization for ORDER BY
18
- # queries hasn't yet merged into Rails.
20
+ # queries isn't in less current versions of Rails.
19
21
  # See: https://github.com/rails/rails/pull/13008
20
- order_by = params[:values].map do |value|
21
- "#{table}.#{attribute}='#{value}' DESC"
22
- end.join(", ")
22
+ conditions = params[:values].map do |value|
23
+ raise OrderAsSpecified::Error, "Cannot order by `nil`" if value.nil?
23
24
 
24
- order(order_by)
25
+ "#{table}.#{attribute}='#{value}'"
26
+ end
27
+
28
+ scope = order(conditions.map { |cond| "#{cond} DESC" }.join(", "))
29
+
30
+ if distinct_on
31
+ scope = scope.select("DISTINCT ON (#{conditions.join(', ')}) #{table}.*")
32
+ end
33
+
34
+ scope
25
35
  end
26
36
 
27
37
  private
@@ -0,0 +1,4 @@
1
+ module OrderAsSpecified
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module OrderAsSpecified
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0"
3
3
  end
@@ -19,13 +19,14 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "activerecord", "~> 4.0"
22
+ spec.add_dependency "activerecord", ">= 4.0"
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.7"
25
25
  spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4"
26
26
  spec.add_development_dependency "overcommit", "~> 0.23"
27
+ spec.add_development_dependency "pg", ">= 0.18"
27
28
  spec.add_development_dependency "rspec", "~> 3.2"
28
29
  spec.add_development_dependency "rspec-rails", "~> 3.2"
29
30
  spec.add_development_dependency "rubocop", "~> 0.29"
30
- spec.add_development_dependency "temping", "~> 3.2"
31
+ spec.add_development_dependency "sqlite3", ">= 1.3"
31
32
  end
@@ -0,0 +1,9 @@
1
+ sqlite3_test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+ timeout: 500
5
+
6
+ postgresql_test:
7
+ adapter: postgresql
8
+ database: order_as_specified_test
9
+ username: postgres
@@ -0,0 +1,18 @@
1
+ class TestSetupMigration < ActiveRecord::Migration
2
+ def up
3
+ return if ActiveRecord::Base.connection.table_exists? :test_classes
4
+
5
+ create_table :test_classes do |t|
6
+ t.string :field
7
+ end
8
+
9
+ create_table :association_test_classes do |t|
10
+ t.integer :test_class_id
11
+ end
12
+ end
13
+
14
+ def down
15
+ drop_table :test_classes
16
+ drop_table :association_test_classes
17
+ end
18
+ end
@@ -1,87 +1,5 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe OrderAsSpecified do
4
- describe ".order_as_specified" do
5
- Temping.create :test_class do
6
- with_columns do |t|
7
- t.string :field
8
- end
9
-
10
- extend OrderAsSpecified
11
-
12
- has_one :association_test_class
13
- end
14
-
15
- Temping.create :association_test_class do
16
- with_columns do |t|
17
- t.integer :test_class_id
18
- end
19
-
20
- belongs_to :test_class
21
- end
22
-
23
- # Clean up after each test. This is a lot lighter for these few tests than
24
- # trying to wrangle with RSpec-Rails to get transactional tests to work.
25
- after :each do
26
- TestClass.delete_all
27
- AssociationTestClass.delete_all
28
- end
29
-
30
- let(:shuffled_objects) do
31
- 5.times.map { |i| TestClass.create(field: "Field #{i}") }.shuffle
32
- end
33
- let(:shuffled_object_fields) { shuffled_objects.map(&:field) }
34
- let(:shuffled_object_ids) { shuffled_objects.map(&:id) }
35
- let(:omitted_object) { TestClass.create }
36
-
37
- context "with no table name specified" do
38
- subject { TestClass.order_as_specified(field: shuffled_object_fields) }
39
-
40
- it "returns results including unspecified objects" do
41
- omitted_object # Build an object that isn't sorted in this list.
42
- expect(subject).to include omitted_object
43
- end
44
-
45
- it "returns results in the given order" do
46
- omitted_object # Build an object that isn't sorted in this list.
47
- expect(subject.map(&:id)).
48
- to eq [*shuffled_object_ids, omitted_object.id]
49
- end
50
- end
51
-
52
- context "with another table name specified" do
53
- let(:associated_objects) do
54
- shuffled_objects.map do |object|
55
- AssociationTestClass.create(test_class: object)
56
- end
57
- end
58
- let(:associated_object_ids) { associated_objects.map(&:id) }
59
- let(:omitted_associated_object) do
60
- AssociationTestClass.create(test_class: omitted_object)
61
- end
62
-
63
- subject do
64
- TestClass.
65
- joins(:association_test_class).
66
- order_as_specified(
67
- association_test_classes: { id: associated_object_ids }
68
- )
69
- end
70
-
71
- it "returns results including unspecified objects" do
72
- # Build an object that isn't sorted in this list.
73
- omitted_associated_object
74
-
75
- expect(subject).to include omitted_object
76
- end
77
-
78
- it "returns results in the given order" do
79
- # Build an object that isn't sorted in this list.
80
- omitted_associated_object
81
-
82
- expect(subject.map(&:id)).
83
- to eq [*shuffled_object_ids, omitted_object.id]
84
- end
85
- end
86
- end
87
- end
1
+ # Instead of defining specs here, we define them in
2
+ # shared/order_as_specified_examples.rb. We then have a different spec file for
3
+ # each supported database adapter, whch runs the shared RSpec tests found in
4
+ # the shared/order_as_specified_examples.rb file, along with any adapter-
5
+ # specific tests.
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+ require "shared/order_as_specified_examples"
3
+ require "config/test_setup_migration"
4
+
5
+ RSpec.describe "PostgreSQL" do
6
+ before :all do
7
+ ActiveRecord::Base.establish_connection(:postgresql_test)
8
+ TestSetupMigration.migrate(:up)
9
+ end
10
+
11
+ after(:all) { ActiveRecord::Base.remove_connection }
12
+
13
+ include_examples ".order_as_specified"
14
+
15
+ context "when using DISTINCT ON" do
16
+ subject do
17
+ TestClass.order_as_specified(
18
+ distinct_on: true,
19
+ field: shuffled_object_fields
20
+ )
21
+ end
22
+
23
+ let(:shuffled_objects) do
24
+ fields = 3.times.map { |i| "Field #{i}" } * 2
25
+ 5.times.map { |i| TestClass.create(field: fields[i]) }.shuffle
26
+ end
27
+
28
+ it "returns distinct objects" do
29
+ expect(subject.length).to eq 3
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,80 @@
1
+ require "support/test_class"
2
+ require "support/association_test_class"
3
+
4
+ RSpec.shared_examples ".order_as_specified" do
5
+ # Clean up after each test. This is a lot lighter for these few tests than
6
+ # trying to wrangle with RSpec-Rails to get transactional tests to work.
7
+ after :each do
8
+ TestClass.delete_all
9
+ AssociationTestClass.delete_all
10
+ end
11
+
12
+ let(:shuffled_objects) do
13
+ 5.times.map { |i| TestClass.create(field: "Field #{i}") }.shuffle
14
+ end
15
+ let(:shuffled_object_fields) { shuffled_objects.map(&:field) }
16
+ let(:shuffled_object_ids) { shuffled_objects.map(&:id) }
17
+ let(:omitted_object) { TestClass.create(field: "Nothing") }
18
+
19
+ context "with no table name specified" do
20
+ subject { TestClass.order_as_specified(field: shuffled_object_fields) }
21
+
22
+ it "returns results including unspecified objects" do
23
+ omitted_object # Build an object that isn't sorted in this list.
24
+ expect(subject).to include omitted_object
25
+ end
26
+
27
+ it "returns results in the given order" do
28
+ omitted_object # Build an object that isn't sorted in this list.
29
+ expect(subject.map(&:id)).
30
+ to eq [*shuffled_object_ids, omitted_object.id]
31
+ end
32
+
33
+ context "when the order includes nil" do
34
+ let(:shuffled_objects) do
35
+ 5.times.map do |i|
36
+ TestClass.create(field: (i == 0 ? nil : "Field #{i}"))
37
+ end.shuffle
38
+ end
39
+
40
+ it "raises an error" do
41
+ expect { subject }.to raise_error(OrderAsSpecified::Error)
42
+ end
43
+ end
44
+ end
45
+
46
+ context "with another table name specified" do
47
+ subject do
48
+ TestClass.
49
+ joins(:association_test_class).
50
+ order_as_specified(
51
+ association_test_classes: { id: associated_object_ids }
52
+ )
53
+ end
54
+
55
+ let(:associated_objects) do
56
+ shuffled_objects.map do |object|
57
+ AssociationTestClass.create(test_class: object)
58
+ end
59
+ end
60
+ let(:associated_object_ids) { associated_objects.map(&:id) }
61
+ let(:omitted_associated_object) do
62
+ AssociationTestClass.create(test_class: omitted_object)
63
+ end
64
+
65
+ it "returns results including unspecified objects" do
66
+ # Build an object that isn't sorted in this list.
67
+ omitted_associated_object
68
+
69
+ expect(subject).to include omitted_object
70
+ end
71
+
72
+ it "returns results in the given order" do
73
+ # Build an object that isn't sorted in this list.
74
+ omitted_associated_object
75
+
76
+ expect(subject.map(&:id)).
77
+ to eq [*shuffled_object_ids, omitted_object.id]
78
+ end
79
+ end
80
+ end
@@ -1,13 +1,12 @@
1
1
  require "codeclimate-test-reporter"
2
2
  CodeClimate::TestReporter.start
3
3
 
4
- # Connect to an in-memory database for ActiveRecord tests.
5
- require "temping"
6
- ActiveRecord::Base.
7
- establish_connection(adapter: "sqlite3", database: ":memory:")
4
+ require "active_record"
8
5
 
9
6
  require "order_as_specified"
10
7
 
8
+ ActiveRecord::Base.configurations = YAML.load_file("spec/config/database.yml")
9
+
11
10
  RSpec.configure do |config|
12
11
  # These two settings work together to allow you to limit a spec run
13
12
  # to individual examples or groups you care about by tagging them with
@@ -0,0 +1,14 @@
1
+ require "spec_helper"
2
+ require "shared/order_as_specified_examples"
3
+ require "config/test_setup_migration"
4
+
5
+ RSpec.describe "SQLite3" do
6
+ before :all do
7
+ ActiveRecord::Base.establish_connection(:sqlite3_test)
8
+ TestSetupMigration.migrate(:up)
9
+ end
10
+
11
+ after(:all) { ActiveRecord::Base.remove_connection }
12
+
13
+ include_examples ".order_as_specified"
14
+ end
@@ -0,0 +1,5 @@
1
+ class AssociationTestClass < ActiveRecord::Base
2
+ extend OrderAsSpecified
3
+
4
+ belongs_to :test_class
5
+ end
@@ -0,0 +1,5 @@
1
+ class TestClass < ActiveRecord::Base
2
+ extend OrderAsSpecified
3
+
4
+ has_one :association_test_class
5
+ end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: order_as_specified
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: '1.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Evelyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-13 00:00:00.000000000 Z
11
+ date: 2016-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ~>
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.23'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0.18'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0.18'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rspec
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -109,19 +123,19 @@ dependencies:
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0.29'
111
125
  - !ruby/object:Gem::Dependency
112
- name: temping
126
+ name: sqlite3
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
- - - ~>
129
+ - - '>='
116
130
  - !ruby/object:Gem::Version
117
- version: '3.2'
131
+ version: '1.3'
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
- - - ~>
136
+ - - '>='
123
137
  - !ruby/object:Gem::Version
124
- version: '3.2'
138
+ version: '1.3'
125
139
  description: Obtain ActiveRecord results with a custom ordering with no need to store
126
140
  anything in the database.
127
141
  email:
@@ -138,10 +152,18 @@ files:
138
152
  - LICENSE.txt
139
153
  - README.md
140
154
  - lib/order_as_specified.rb
155
+ - lib/order_as_specified/error.rb
141
156
  - lib/order_as_specified/version.rb
142
157
  - order_as_specified.gemspec
158
+ - spec/config/database.yml
159
+ - spec/config/test_setup_migration.rb
143
160
  - spec/order_as_specified_spec.rb
161
+ - spec/postgresql_spec.rb
162
+ - spec/shared/order_as_specified_examples.rb
144
163
  - spec/spec_helper.rb
164
+ - spec/sqlite3_spec.rb
165
+ - spec/support/association_test_class.rb
166
+ - spec/support/test_class.rb
145
167
  homepage: https://github.com/panorama-ed/order_as_specified
146
168
  licenses:
147
169
  - MIT
@@ -167,5 +189,13 @@ signing_key:
167
189
  specification_version: 4
168
190
  summary: Add arbitrary ordering to ActiveRecord queries.
169
191
  test_files:
192
+ - spec/config/database.yml
193
+ - spec/config/test_setup_migration.rb
170
194
  - spec/order_as_specified_spec.rb
195
+ - spec/postgresql_spec.rb
196
+ - spec/shared/order_as_specified_examples.rb
171
197
  - spec/spec_helper.rb
198
+ - spec/sqlite3_spec.rb
199
+ - spec/support/association_test_class.rb
200
+ - spec/support/test_class.rb
201
+ has_rdoc: