sort_by_str 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2010 The New York Times Company
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this library except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,35 @@
1
+ = sort_by_str
2
+
3
+ SQL-like sorts on your Enumerables.
4
+
5
+ == Getting Started
6
+
7
+ gem install sort_by_str
8
+ require 'sort_by_str'
9
+
10
+ == Usage
11
+
12
+ a = Date.parse('2010-08-22')
13
+ b = Date.parse('2010-08-23)
14
+
15
+ [a,b].sort_by_str('year ASC, day DESC')
16
+ => [b,a]
17
+
18
+ Call sort_by_str on your Enumerable with a SQL-style sort expression containing a list of fields.
19
+
20
+ A basic expression might look like like
21
+ 'year, month, day'.
22
+
23
+ Optional ASC (ascending) or DESC (descending) modifiers can also be used:
24
+ 'year ASC, day DESC'
25
+
26
+ <tt>send</tt> is used to extract values for comparison, so any valid method name
27
+ can be used in the expression.
28
+
29
+ == Author
30
+
31
+ Ben Koski, bkoski@nytimes.com
32
+
33
+ == Copyright
34
+
35
+ Copyright (c) 2010 The New York Times Company. See LICENSE for details.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sort_by_str"
8
+ gem.summary = %Q{SQL-like sorts on your Enumerables}
9
+ gem.description = %Q{SQL-like sorts on your Enumerables}
10
+ gem.email = "bkoski@nytimes.com"
11
+ gem.homepage = "http://github.com/nytimes/sort_by_str"
12
+ gem.authors = ["Ben Koski"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION')
47
+ version = File.read('VERSION')
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "sort_by_str #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,64 @@
1
+ module Enumerable
2
+
3
+ # Similar to sort_by, sort_by_str accepts a string containing a SQL-style sort expression.
4
+ #
5
+ # The simplest sort expression is a comma separated list of fields: <tt>'month,day,year'</tt>
6
+ #
7
+ # But can also include optional ASC (ascending) or DESC (descending) order modifiers.
8
+ # For example: <tt>'year DESC, month ASC, day ASC'</tt>
9
+ # If an order modifier is omitted for a field, an ASC sort is assumed.
10
+ #
11
+ # Field values are checked for comparision using a <tt>send</tt>, so any method name can be used.
12
+ def sort_by_str str
13
+ sort_parts = str.split(',').collect { |p| p.split(' ') }
14
+
15
+ # double-check structure of parsed data
16
+ raise ArgumentError, "'#{str}' doesn't appear correctly formatted." if sort_parts.empty?
17
+ sort_parts.each { |p| raise ArgumentError, "'#{str}' doesn't appear correctly formatted." if p.length > 2 }
18
+
19
+ # split into list of fields and sort directions for those fields
20
+ fields = sort_parts.collect { |p| p.first }
21
+ sort_directions = sort_parts.collect { |p| p.last.upcase == 'DESC' ? :desc : :asc } # if unspecified assumed to be an ASC sort
22
+
23
+ # From here we follow pattern of Schwartzian Transform used in sort_by (http://bit.ly/aPEsNO).
24
+ # This means that rather than doing a sort! directly on self, we cache values to be sorted into an array.
25
+ # So for example, given:
26
+ # a = Date.today
27
+ # b = Date.today + 1
28
+ # [a,b].sort_by_str('year ASC, day DESC')
29
+ #
30
+ # we transform to
31
+ # [[a, 2010, 22],[b,2010,23]]
32
+ # and then proceed with sort.
33
+ # Following sort, we collect list of first elements to produce final, sorted array.
34
+ # This prevents sort attrs from being called multiple times during sort
35
+ # which could be problematic if sort attr is an expensive calcuation.
36
+
37
+ # 1. collect data into intermediate arrays, data element first
38
+ sort_data = collect do |element|
39
+ data = [element]
40
+ fields.each { |f| data << element.send(f) }
41
+ data
42
+ end
43
+
44
+ # 2. actually sort the data -- a,b are limited to a[1..-1] so as to exclude first [data] element
45
+ sort_data.sort! do |a,b|
46
+ cmp = 0
47
+ sort_directions.zip(a[1..-1], b[1..-1]) do |field_data|
48
+ direction, a_val, b_val = field_data
49
+ if direction == :desc
50
+ cmp = b_val <=> a_val
51
+ else
52
+ cmp = a_val <=> b_val
53
+ end
54
+ break if cmp != 0
55
+ end
56
+
57
+ cmp
58
+ end
59
+
60
+ # 3. now that data is sorted, reduce back to list of data elements
61
+ sort_data.collect { |element| element.first }
62
+ end
63
+
64
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'ostruct'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'sort_by_str'
9
+
10
+ class Test::Unit::TestCase
11
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ class SortByStrTest < Test::Unit::TestCase
4
+
5
+ context "with valid sort expression" do
6
+
7
+ setup do
8
+ @red_10 = OpenStruct.new(:color => 'red', :size => 10)
9
+ @blue_10 = OpenStruct.new(:color => 'blue', :size => 10)
10
+ @red_15 = OpenStruct.new(:color => 'red', :size => 15)
11
+
12
+ @all_data = [@red_10, @blue_10, @red_15]
13
+ end
14
+
15
+ should "sort correctly on a single-field sort" do
16
+ assert_equal [@blue_10, @red_15], [@red_15, @blue_10].sort_by_str('size')
17
+ end
18
+
19
+ should "sort correctly on a multi-field sort" do
20
+ assert_equal [@blue_10, @red_10, @red_15], @all_data.sort_by_str('size, color')
21
+ end
22
+
23
+ should "respect DESC modifier" do
24
+ assert_equal [@red_15, @blue_10, @red_10], @all_data.sort_by_str('size DESC, color')
25
+ end
26
+
27
+ should "respect ASC modifier" do
28
+ assert_equal [@blue_10, @red_10, @red_15], @all_data.sort_by_str('size ASC, color')
29
+ end
30
+
31
+ should "sort correctly even if extra spaces are supplied" do
32
+ assert_equal [@blue_10, @red_10, @red_15], @all_data.sort_by_str('size ASC , color ')
33
+ end
34
+
35
+ end
36
+
37
+ context "with invalid sort expression" do
38
+ setup do
39
+ @data = [OpenStruct.new(:size => 10), OpenStruct.new(:size => 15)]
40
+ end
41
+
42
+ should "raise an ArgumentError if too many tokens are supplied for a field" do
43
+ assert_raises(ArgumentError) { @data.sort('size ASC DESC')}
44
+ end
45
+
46
+ should "raise an ArgumentError if no fields are supplied" do
47
+ assert_raises(ArgumentError) { @data.sort('') }
48
+ end
49
+
50
+ should "raise an ArgumentError if no tokens are supplied for a field" do
51
+ assert_raises(ArgumentError) { @data.sort('size ASC, ,size DESC')}
52
+ end
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sort_by_str
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Koski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-08-24 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: SQL-like sorts on your Enumerables
26
+ email: bkoski@nytimes.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - lib/sort_by_str.rb
42
+ - test/test_helper.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/nytimes/sort_by_str
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: SQL-like sorts on your Enumerables
71
+ test_files:
72
+ - test/test_helper.rb
73
+ - test/test_sort_by_str.rb