order_as_specified 0.1.0 → 1.0

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