active_record_arrangeable 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eef7092e6af2f283d12896673fb48def675ca978f6ec0d94742efe25c9d51cd9
4
+ data.tar.gz: 42c1bb3be2c742724e757e1af0c80ca7d8713c96af085b488a02fa2d66961b83
5
+ SHA512:
6
+ metadata.gz: fcaef282224e196f7ed7d9fd1d333593dbfd4ef7e0050a1ea4356cd0a24a6a92fa5dc6b4dd42c24c61285dc1243537a6135d5bf4d6478a8ed98eb812527ac969
7
+ data.tar.gz: b973bbf1506f88ecea5111d933b1db283b6178c1270459c6181f046d35591f0c6b2ab02e18c55cf09365e913f78ee19647800ce1ae718456a3984ab88a667038
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ test.sqlite3
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ db
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ sudo: false
2
+ language: ruby
3
+
4
+ cache:
5
+ directories:
6
+ - vendor/bundle
7
+
8
+ rvm:
9
+ - 2.5.1
10
+
11
+ bundler_args: --jobs 3 --retry 3
12
+
13
+ script: bundle exec rspec
14
+
15
+ gemfile:
16
+ - Gemfile
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'pg', '>= 0.18', '< 2.0'
7
+ gem 'mysql2', '>= 0.5.0', '< 0.6.0'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Rafael Jurado
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # ActiveRecord::Filterable
2
+ ![Build Status](https://travis-ci.org/pacop/active_record_arrangeable.svg?branch=master)
3
+
4
+ Let you add scopes to active_record document for filters.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'active_record_arrangeable'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install active_record_arrangeable
19
+
20
+ ## Usage
21
+
22
+ #### Model
23
+
24
+ ```ruby
25
+ class City < ActiveRecord::Base
26
+ include ActiveRecord::Arrangeable
27
+
28
+ has_many :provinces
29
+
30
+ arrange_by :province_name, (lambda do |direction=:asc|
31
+ includes(:provinces).order("provinces.name #{direction}")
32
+ end)
33
+ end
34
+ class Province < ActiveRecord::Base
35
+ belongs_to :city
36
+ end
37
+
38
+ City.create(name: 'city2', provinces: [Province.create(name: 'c3'),
39
+ Province.create(name: 'd4')])
40
+ City.create(name: 'city1', provinces: [Province.create(name: 'a1'),
41
+ Province.create(name: 'b2')])
42
+ City.create(name: 'city3', provinces: [Province.create(name: 'a1'),
43
+ Province.create(name: 'b2')])
44
+
45
+ City.arrange(:province_name) => [city1, city3, city2]
46
+ City.arrange(province_name: :desc) => [city2, city1, city3]
47
+ City.arrange(:province_name, :name) => [city1, city3, city2]
48
+ City.arrange(:province_name, name: :desc) => [city3, city1, city2]
49
+ ```
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it ( http://github.com/<my-github-username>/active_record_arrange/fork )
54
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
55
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
56
+ 4. Push to the branch (`git push origin my-new-feature`)
57
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_record_arrangeable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'active_record_arrangeable'
8
+ spec.version = ActiveRecord::Arrangeable::VERSION
9
+ spec.authors = ['Francisco Padillo']
10
+ spec.email = ['pacopa.93@gmail.com']
11
+ spec.summary = 'Friendly and easy sort'
12
+ spec.description = 'Friendly and easy sort'
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.5'
22
+ spec.add_development_dependency 'rspec'
23
+ spec.add_development_dependency 'sqlite3'
24
+ spec.add_dependency 'activerecord', ['>= 3.0', '<= 5.2']
25
+ end
@@ -0,0 +1,3 @@
1
+ require 'active_record_arrangeable/version'
2
+ require 'active_record_arrangeable/arrangeable'
3
+ require 'active_record_arrangeable/arrange_service'
@@ -0,0 +1,68 @@
1
+ module ActiveRecord
2
+ module Arrangeable
3
+ class ArrangeService
4
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
5
+ "asc", "desc", "ASC", "DESC"].freeze
6
+
7
+ DEFAULT_DIRECTION = :asc
8
+
9
+ def self.arrange(relation, *args)
10
+ self.new(relation).arrange!(args)
11
+ end
12
+
13
+ def initialize(relation)
14
+ @relation = is_a?(ActiveRecord::Relation) ? relation : relation.all
15
+ end
16
+
17
+ def arrange!(*args)
18
+ preproces_args(args)
19
+
20
+ args.each do |arg|
21
+ if arg.is_a?(Hash)
22
+ arg.each do |field, value|
23
+ apply_arrange(field, value)
24
+ end
25
+ else
26
+ apply_arrange(arg, DEFAULT_DIRECTION)
27
+ end
28
+ end
29
+
30
+ @relation
31
+ end
32
+
33
+ private
34
+
35
+ def apply_arrange(field, direction)
36
+ method_name = "arrange_with_#{field}"
37
+
38
+ @relation = if @relation.respond_to?(method_name)
39
+ @relation.public_send(method_name, direction)
40
+ else
41
+ @relation.order(field => direction)
42
+ end
43
+ end
44
+
45
+ def preproces_args(order_args)
46
+ order_args.map! do |arg|
47
+ @relation.sanitize_sql_for_order(arg)
48
+ end
49
+ order_args.flatten!
50
+
51
+ validate_direction_args(order_args)
52
+ end
53
+
54
+ def validate_direction_args(args)
55
+ args.each do |arg|
56
+ next unless arg.is_a?(Hash)
57
+ arg.each do |_key, value|
58
+ unless VALID_DIRECTIONS.include?(value)
59
+ raise ArgumentError,
60
+ "Direction \"#{value}\" is invalid. Valid directions are: "\
61
+ "#{VALID_DIRECTIONS.to_a.inspect}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveRecord
2
+ module Arrangeable
3
+ def self.included(base)
4
+ base.extend ArrangeClassMethods
5
+
6
+ base.class_eval do
7
+ ##
8
+ # Adds custom arrange as scope
9
+ #
10
+ private_class_method def self.arrange_by(attr, arrange)
11
+ scope "arrange_with_#{attr}", arrange
12
+ end
13
+ end
14
+
15
+ return unless defined?(ActiveRecord::Relation)
16
+
17
+ ActiveRecord::Relation.send :include, ArrangeClassMethods
18
+ end
19
+
20
+ module ArrangeClassMethods
21
+ ##
22
+ # Applies params sorts to current scope
23
+ #
24
+ def arrange(*args)
25
+ ArrangeService.arrange(self, *args)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module Arrangeable
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveRecord::Arrangeable do
4
+ class City < ActiveRecord::Base
5
+ include ActiveRecord::Arrangeable
6
+
7
+ scope :actives, ->() { where(active: true) }
8
+
9
+ has_many :provinces
10
+
11
+ arrange_by :province_name, (lambda do |direction=:asc|
12
+ includes(:provinces).order("provinces.name #{direction}")
13
+ end)
14
+ end
15
+ class Province < ActiveRecord::Base
16
+ belongs_to :city
17
+ end
18
+
19
+ before do
20
+ City.unscoped.destroy_all
21
+ end
22
+
23
+ {model: City, all: City.all, scope: City.actives}.each do |explanation, subject|
24
+ describe explanation do
25
+ let(:subject_sort_by) { subject.respond_to?(:subject) ? subject : subject.all }
26
+
27
+ context 'when custom arrange_by is used in isolation' do
28
+ let(:sorted_cities) { subject_sort_by.sort_by { |city| city.provinces.map(&:name) } }
29
+
30
+ before do
31
+ City.create(active: false)
32
+ City.create(name: 'city2', provinces: [Province.create(name: 'c3'),
33
+ Province.create(name: 'd4')])
34
+ City.create(name: 'city1', provinces: [Province.create(name: 'a1'),
35
+ Province.create(name: 'b2')])
36
+ end
37
+
38
+ context 'with symbol' do
39
+ it 'sorts ascending by default' do
40
+ expect(subject.arrange(:province_name).map(&:id)).to eq(sorted_cities.map(&:id))
41
+ end
42
+ end
43
+
44
+ context 'with string' do
45
+ it 'sorts ascending by default' do
46
+ expect(subject.arrange('province_name').map(&:id)).to eq(sorted_cities.map(&:id))
47
+ end
48
+ end
49
+
50
+ context 'with hash' do
51
+ it 'sorts descending' do
52
+ expect(subject.arrange(province_name: :desc).map(&:id)).to eq(
53
+ sorted_cities.map(&:id).reverse
54
+ )
55
+ end
56
+
57
+ it 'sorts ascending' do
58
+ expect(subject.arrange(province_name: :asc).map(&:id)).to eq(sorted_cities.map(&:id))
59
+ end
60
+ end
61
+
62
+ context 'with an array' do
63
+ it 'sorts descending' do
64
+ expect(subject.arrange([province_name: :desc]).map(&:id)).to eq(
65
+ sorted_cities.map(&:id).reverse
66
+ )
67
+ end
68
+
69
+ it 'sorts ascending' do
70
+ expect(subject.arrange([province_name: :asc]).map(&:id)).to eq(sorted_cities.map(&:id))
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'when custom arrange_by is combined with attrs' do
76
+ let(:sorted_cities_asc) do
77
+ subject_sort_by.sort_by { |city| [city.provinces.map(&:name), city.name] }.map(&:id)
78
+ end
79
+ let(:sorted_cities_desc) do
80
+ subject_sort_by.sort_by { |city| [city.provinces.map(&:name), city.name] }.map(&:id).reverse
81
+ end
82
+ let(:sorted_cities_desc_asc) do
83
+ subject_sort_by.sort do |c1, c2|
84
+ [c2.provinces.map(&:name), c1.name] <=> [c1.provinces.map(&:name), c2.name]
85
+ end.map(&:id)
86
+ end
87
+ let(:sorted_cities_asc_desc) do
88
+ subject_sort_by.sort do |c1, c2|
89
+ [c1.provinces.map(&:name), c2.name] <=> [c2.provinces.map(&:name), c1.name]
90
+ end.map(&:id)
91
+ end
92
+
93
+ before do
94
+ City.create(name: 'city2', provinces: [Province.create(name: 'c3'),
95
+ Province.create(name: 'd4')])
96
+ City.create(name: 'city3', provinces: [Province.create(name: 'a1'),
97
+ Province.create(name: 'b2')])
98
+ City.create(name: 'city1', provinces: [Province.create(name: 'a1'),
99
+ Province.create(name: 'b2')])
100
+ end
101
+
102
+ it 'sorts ascending' do
103
+ expect(subject.arrange([province_name: :asc], name: :asc).map(&:id)).to eq(sorted_cities_asc)
104
+ end
105
+
106
+ it 'sorts descending' do
107
+ expect(subject.arrange([province_name: :desc], name: :desc).map(&:id)).to eq(
108
+ sorted_cities_desc
109
+ )
110
+ end
111
+
112
+ it 'provinces desc, name asc' do
113
+ expect(subject.arrange([province_name: :desc], name: :asc).map(&:id)).to eq(
114
+ sorted_cities_desc_asc
115
+ )
116
+ end
117
+
118
+ it 'provinces asc, name desc' do
119
+ expect(subject.arrange([province_name: :asc], name: :desc).map(&:id)).to eq(
120
+ sorted_cities_asc_desc
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,97 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+
3
+ require 'bundler/setup'
4
+ require 'active_record'
5
+
6
+ require 'active_record_arrangeable'
7
+
8
+ ActiveRecord::Base.establish_connection(
9
+ adapter: 'sqlite3', database: 'db/active_record_arrangeablablee.sqlite3'
10
+ )
11
+
12
+ ActiveRecord::Base.connection.create_table(:cities, force: true, collate: :nocase) do |t|
13
+ t.string :name
14
+ t.boolean :active, default: true
15
+ t.timestamps null: false
16
+ end
17
+
18
+ ActiveRecord::Base.connection.create_table(:provinces, force: true, collate: :nocase) do |t|
19
+ t.string :name
20
+ t.belongs_to :city
21
+
22
+ t.timestamps null: false
23
+ end
24
+
25
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
26
+
27
+ RSpec.configure do |config|
28
+ # rspec-expectations config goes here. You can use an alternate
29
+ # assertion/expectation library such as wrong or the stdlib/minitest
30
+ # assertions if you prefer.
31
+ config.expect_with :rspec do |expectations|
32
+ # This option will default to `true` in RSpec 4. It makes the `description`
33
+ # and `failure_message` of custom matchers include text for helper methods
34
+ # defined using `chain`, e.g.:
35
+ # be_bigger_than(2).and_smaller_than(4).description
36
+ # # => "be bigger than 2 and smaller than 4"
37
+ # ...rather than:
38
+ # # => "be bigger than 2"
39
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
40
+ end
41
+
42
+ # rspec-mocks config goes here. You can use an alternate test double
43
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
44
+ config.mock_with :rspec do |mocks|
45
+ # Prevents you from mocking or stubbing a method that does not exist on
46
+ # a real object. This is generally recommended, and will default to
47
+ # `true` in RSpec 4.
48
+ mocks.verify_partial_doubles = true
49
+ end
50
+
51
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
52
+ # have no way to turn it off -- the option exists only for backwards
53
+ # compatibility in RSpec 3). It causes shared context metadata to be
54
+ # inherited by the metadata hash of host groups and examples, rather than
55
+ # triggering implicit auto-inclusion in groups with matching metadata.
56
+ config.shared_context_metadata_behavior = :apply_to_host_groups
57
+
58
+ # This allows you to limit a spec run to individual examples or groups
59
+ # you care about by tagging them with `:focus` metadata. When nothing
60
+ # is tagged with `:focus`, all examples get run. RSpec also provides
61
+ # aliases for `it`, `describe`, and `context` that include `:focus`
62
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
63
+ config.filter_run_when_matching :focus
64
+ # Allows RSpec to persist some state between runs in order to support
65
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
66
+ # you configure your source control system to ignore this file.
67
+ # config.example_status_persistence_file_path = 'spec/examples.txt'
68
+ # Limits the available syntax to the non-monkey patched syntax that is
69
+ # recommended. For more details, see:
70
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
71
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
72
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
73
+ config.disable_monkey_patching!
74
+ # Many RSpec users commonly either run the entire suite or an individual
75
+ # file, and it's useful to allow more verbose output when running an
76
+ # individual spec file.
77
+ if config.files_to_run.one?
78
+ # Use the documentation formatter for detailed output,
79
+ # unless a formatter has already been configured
80
+ # (e.g. via a command-line flag).
81
+ config.default_formatter = 'doc'
82
+ end
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ config.profile_examples = 10
87
+ # Run specs in random order to surface order dependencies. If you find an
88
+ # order dependency and want to debug it, you can fix the order by providing
89
+ # the seed, which is printed after each run.
90
+ # --seed 1234
91
+ config.order = :random
92
+ # Seed global randomization in this process using the `--seed` CLI option.
93
+ # Setting this allows you to use `--seed` to deterministically reproduce
94
+ # test failures related to randomization by passing the same `--seed` value
95
+ # as the one that triggered the failure.
96
+ Kernel.srand config.seed
97
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_record_arrangeable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Francisco Padillo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activerecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ - - "<="
63
+ - !ruby/object:Gem::Version
64
+ version: '5.2'
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '3.0'
72
+ - - "<="
73
+ - !ruby/object:Gem::Version
74
+ version: '5.2'
75
+ description: Friendly and easy sort
76
+ email:
77
+ - pacopa.93@gmail.com
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - ".gitignore"
83
+ - ".travis.yml"
84
+ - Gemfile
85
+ - LICENSE.txt
86
+ - README.md
87
+ - Rakefile
88
+ - active_record_arrangeable.gemspec
89
+ - lib/active_record_arrangeable.rb
90
+ - lib/active_record_arrangeable/arrange_service.rb
91
+ - lib/active_record_arrangeable/arrangeable.rb
92
+ - lib/active_record_arrangeable/version.rb
93
+ - spec/arrangeable_spec.rb
94
+ - spec/spec_helper.rb
95
+ homepage: ''
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.7.3
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Friendly and easy sort
119
+ test_files:
120
+ - spec/arrangeable_spec.rb
121
+ - spec/spec_helper.rb