sort_this 1.0.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.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/.rvmrc +48 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +7 -0
- data/lib/sort_this/action_controller.rb +31 -0
- data/lib/sort_this/active_record.rb +100 -0
- data/lib/sort_this/railtie.rb +18 -0
- data/lib/sort_this/version.rb +3 -0
- data/lib/sort_this/view_helpers/action_view.rb +16 -0
- data/lib/sort_this.rb +12 -0
- data/sort_this.gemspec +29 -0
- data/spec/action_controller_spec.rb +80 -0
- data/spec/active_record_spec.rb +219 -0
- data/spec/factories.rb +18 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/.gitkeep +0 -0
- data/spec/support/models.rb +50 -0
- data/spec/view_helpers/action_view_spec.rb +71 -0
- metadata +208 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.rvmrc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
+
environment_id="ruby-1.9.3-p327@sort_this"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.17.3 (stable)" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
else
|
29
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
+
rvm --create "$environment_id" || {
|
31
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
+
return 1
|
33
|
+
}
|
34
|
+
fi
|
35
|
+
|
36
|
+
# If you use bundler, this might be useful to you:
|
37
|
+
# if [[ -s Gemfile ]] && {
|
38
|
+
# ! builtin command -v bundle >/dev/null ||
|
39
|
+
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
|
40
|
+
# }
|
41
|
+
# then
|
42
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
+
# gem install bundler
|
44
|
+
# fi
|
45
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
+
# then
|
47
|
+
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
|
48
|
+
# fi
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Scott Pullen
|
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,82 @@
|
|
1
|
+
# SortThis
|
2
|
+
|
3
|
+
SortThis provides a way to sort.
|
4
|
+
|
5
|
+
The controller/view code is based off of railscasts [episode 228 - Sortable Table Columns](http://railscasts.com/episodes/228-sortable-table-columns).
|
6
|
+
|
7
|
+
Here is an [example app](https://github.com/spullen/sort_example) that demonstrates the usage.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'sort_this', :git => 'git://github.com/spullen/sort_this.git'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Model
|
22
|
+
|
23
|
+
Add a call to the `sortable` method with a hash of sorts
|
24
|
+
|
25
|
+
class YourModel < ActiveRecord::Base
|
26
|
+
|
27
|
+
sort_this :sort_name => {:column_name => :name, :default => 'ASC', :joins => :association, :clause => "some.clause"}
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
The options are
|
32
|
+
|
33
|
+
sort_name: Is the name of the sort, this in most cases will be the same as the column_name, but can be anything
|
34
|
+
|
35
|
+
Sort Options:
|
36
|
+
|
37
|
+
column_name: (Required) The name of the column to sort on.
|
38
|
+
default: (Optional) Defines a default sort if provided. The valid options are 'ASC' or 'DESC'.
|
39
|
+
joins: (Optional) Defines an association to join on, this should be provided if the column is in another table.
|
40
|
+
clause: (Optional) Overrides the clause used for the sort.
|
41
|
+
|
42
|
+
Then to sort
|
43
|
+
|
44
|
+
YourModel.sort("sort_name", "asc|desc") => sorted list of YourModel objects
|
45
|
+
|
46
|
+
### Controller
|
47
|
+
|
48
|
+
In you controller define a default sort
|
49
|
+
|
50
|
+
class SomeController < ApplicationController
|
51
|
+
sortable "sort_name"
|
52
|
+
|
53
|
+
def index
|
54
|
+
SomeModel.sort(sort_column, sort_direction)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
### In Views
|
59
|
+
|
60
|
+
sortable(sort_name, title = nil, html_options = {})
|
61
|
+
|
62
|
+
<%= sortable("sort_name") %>
|
63
|
+
|
64
|
+
or custom title
|
65
|
+
|
66
|
+
<%= sortable("sort_name", "Sort Different Name") %>
|
67
|
+
|
68
|
+
## TODO:
|
69
|
+
|
70
|
+
- Add error handling to active record sortable
|
71
|
+
- Add the ability to customize the sort and direction parameters
|
72
|
+
- Define scopes for each sort defined (individual sort scopes)
|
73
|
+
|
74
|
+
- Testing on different databases (should probably hit postgresql and mysql unless the way SQLite handles it in the same way)
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
|
3
|
+
module SortThis
|
4
|
+
module ActionController
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ClassMethods
|
8
|
+
class_attribute :default_sort_column
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def sortable(sort_column)
|
15
|
+
self.default_sort_column = sort_column
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def sort_column
|
23
|
+
params[:sort].blank? ? self.default_sort_column : params[:sort]
|
24
|
+
end
|
25
|
+
|
26
|
+
def sort_direction
|
27
|
+
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'memoist'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module SortThis
|
5
|
+
module ActiveRecord
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
|
10
|
+
class << self; extend Memoist; self; end.memoize :sort
|
11
|
+
|
12
|
+
|
13
|
+
class_attribute :sort_columns, :default_sort_columns
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
#
|
20
|
+
# Input is a hash of sort_name => sort_options pairs
|
21
|
+
#
|
22
|
+
# :column_name => (required) Column name to sort on
|
23
|
+
# :default => (optional) 'ASC'|'DESC'
|
24
|
+
# :joins => (optional) Association to join on. Note: must be an association of the model being sorted.
|
25
|
+
# Prefixes the column_name with the table name to prevent collisions
|
26
|
+
# :clause => (optional) Override the clause of the sort
|
27
|
+
#
|
28
|
+
# ex.
|
29
|
+
# products have many quotes
|
30
|
+
# vendors have many quotes
|
31
|
+
#
|
32
|
+
# sorting on the quotes table
|
33
|
+
#
|
34
|
+
# :sort_name1 => { :column_name => :quantity }
|
35
|
+
# :sort_name2 => { :column_name => :price, :default => 'DESC' }
|
36
|
+
# :sort_name3 => { :column_name => :name, :joins => :product }
|
37
|
+
#
|
38
|
+
def sort_this(sorts = {})
|
39
|
+
self.sort_columns = {}
|
40
|
+
self.default_sort_columns = {}
|
41
|
+
|
42
|
+
empty_sort_options = { :column_name => nil, :default => nil, :joins => nil, :clause => nil }
|
43
|
+
|
44
|
+
sorts.each do |sort_name, sort_options|
|
45
|
+
sort_options = empty_sort_options.merge(sort_options)
|
46
|
+
|
47
|
+
self.sort_columns[sort_name] = sort_options
|
48
|
+
|
49
|
+
column = sort_options[:column_name] # TODO: raise exception if this is nil
|
50
|
+
table = (sort_options[:joins].blank?) ? table_name : sort_options[:joins].to_s.pluralize
|
51
|
+
clause = (sort_options[:clause].blank?) ? "#{table}.#{column}" : sort_options[:clause]
|
52
|
+
|
53
|
+
self.sort_columns[sort_name][:clause] = clause if sort_options[:clause].blank?
|
54
|
+
|
55
|
+
unless sort_options[:default].blank?
|
56
|
+
# TODO: raise exception if the default is not ASC|DESC
|
57
|
+
default_sort_direction = sort_options[:default].upcase
|
58
|
+
self.default_sort_columns[sort_name] = "#{clause} #{default_sort_direction}"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def sort(sort_column = nil, sort_direction = DEFAULT_SORT_DIRECTION)
|
65
|
+
query = scoped
|
66
|
+
|
67
|
+
# sanitize the sort column and direction
|
68
|
+
sort_column = sort_column.to_s.downcase.to_sym
|
69
|
+
sort_direction = sort_direction.upcase
|
70
|
+
sort_direction = (VALID_SORT_DIRECTIONS.include?(sort_direction) ? sort_direction : DEFAULT_SORT_DIRECTION)
|
71
|
+
|
72
|
+
table_joins = []
|
73
|
+
order_clauses = []
|
74
|
+
|
75
|
+
if !sort_column.blank? && sort_columns.include?(sort_column)
|
76
|
+
join = sort_columns[sort_column][:joins]
|
77
|
+
table_joins << join unless join.nil?
|
78
|
+
order_clauses << "#{sort_columns[sort_column][:clause]} #{sort_direction}"
|
79
|
+
end
|
80
|
+
|
81
|
+
default_sorts = default_sort_columns.clone
|
82
|
+
default_sorts.delete(sort_column) unless sort_column.blank? # remove the sort column's default if it exists
|
83
|
+
|
84
|
+
default_sorts.each do |column_name, sort_clause|
|
85
|
+
join = sort_columns[column_name][:joins]
|
86
|
+
tables_joins << join unless join.nil?
|
87
|
+
order_clauses << sort_clause
|
88
|
+
end
|
89
|
+
|
90
|
+
table_joins.uniq!
|
91
|
+
query = query.joins(table_joins) unless table_joins.empty?
|
92
|
+
|
93
|
+
query = query.order(order_clauses.join(', '))
|
94
|
+
|
95
|
+
query
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SortThis
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer 'sortable' do |app|
|
4
|
+
ActiveSupport.on_load :active_record do
|
5
|
+
include SortThis::ActiveRecord
|
6
|
+
end
|
7
|
+
|
8
|
+
ActiveSupport.on_load :action_controller do
|
9
|
+
include SortThis::ActionController
|
10
|
+
helper_method :sort_column, :sort_direction
|
11
|
+
end
|
12
|
+
|
13
|
+
ActiveSupport.on_load :action_view do
|
14
|
+
include SortThis::ViewHelpers::ActionView
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
module SortThis
|
4
|
+
module ViewHelpers
|
5
|
+
module ActionView
|
6
|
+
|
7
|
+
def sortable(sort_name, title = nil, html_options = {})
|
8
|
+
title ||= sort_name.titleize
|
9
|
+
css_class = (sort_name == sort_column) ? "sortable-current sortable-#{sort_direction}" : nil
|
10
|
+
direction = (sort_name == sort_column && sort_direction == "asc") ? "desc" : "asc"
|
11
|
+
link_to title, params.merge(:sort => sort_name, :direction => direction), {:class => css_class}.merge(html_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/sort_this.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'sort_this/version'
|
2
|
+
require 'sort_this/active_record'
|
3
|
+
require 'sort_this/action_controller'
|
4
|
+
require 'sort_this/view_helpers/action_view'
|
5
|
+
require 'sort_this/railtie' if defined?(Rails)
|
6
|
+
|
7
|
+
module SortThis
|
8
|
+
SORT_ASC = 'ASC'
|
9
|
+
SORT_DESC = 'DESC'
|
10
|
+
DEFAULT_SORT_DIRECTION = SORT_ASC
|
11
|
+
VALID_SORT_DIRECTIONS = [SORT_ASC, SORT_DESC]
|
12
|
+
end
|
data/sort_this.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sort_this/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "sort_this"
|
8
|
+
gem.version = SortThis::VERSION
|
9
|
+
gem.authors = ["Scott Pullen"]
|
10
|
+
gem.email = ["s.pullen05@gmail.com"]
|
11
|
+
gem.description = %q{SortThis}
|
12
|
+
gem.summary = %q{SortThis}
|
13
|
+
gem.homepage = "https://github.com/spullen/sort_this"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'rails', '>= 3.0'
|
21
|
+
gem.add_dependency 'memoist', '0.2.0'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'debugger', '1.2.2'
|
24
|
+
gem.add_development_dependency 'rspec', '2.12.0'
|
25
|
+
gem.add_development_dependency 'sqlite3', '1.3.6'
|
26
|
+
gem.add_development_dependency 'factory_girl', '4.1.0'
|
27
|
+
gem.add_development_dependency 'database_cleaner', '0.9.1'
|
28
|
+
gem.add_development_dependency 'capybara', '2.0.1'
|
29
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestController < ActionController::Base
|
4
|
+
include SortThis::ActionController
|
5
|
+
sortable "price"
|
6
|
+
|
7
|
+
def index
|
8
|
+
render :text => 'OK'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# get access to the protected methods
|
13
|
+
TestController.send(:public, *TestController.protected_instance_methods)
|
14
|
+
|
15
|
+
describe SortThis::ActionController do
|
16
|
+
|
17
|
+
it 'should respond to sortable' do
|
18
|
+
TestController.should respond_to(:sortable)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should set the default sort column' do
|
22
|
+
TestController.default_sort_column.should == "price"
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'instance methods' do
|
26
|
+
subject { @controller }
|
27
|
+
|
28
|
+
before(:each) do
|
29
|
+
@controller = TestController.new
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should respond to sort_column' do
|
33
|
+
@controller.should respond_to(:sort_column)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should respond to sort_direction' do
|
37
|
+
@controller.should respond_to(:sort_direction)
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#sort_column' do
|
41
|
+
context 'given that the sort param is not set' do
|
42
|
+
it 'should return the default sort column' do
|
43
|
+
@controller.stub!(:params).and_return({})
|
44
|
+
@controller.sort_column.should == "price"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'given that the sort param is set' do
|
49
|
+
it 'should return the sort provided by the params' do
|
50
|
+
sort = "quantity"
|
51
|
+
@controller.stub!(:params).and_return({:sort => sort})
|
52
|
+
@controller.sort_column.should == sort
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#sort_direction' do
|
58
|
+
context 'given that the direction param is not set' do
|
59
|
+
it 'should return the default sort direction' do
|
60
|
+
@controller.stub!(:params).and_return({})
|
61
|
+
@controller.sort_direction.should == "asc"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'given that the direction param is set' do
|
66
|
+
it 'should return the sort direction provided by the params' do
|
67
|
+
@controller.stub!(:params).and_return({:direction => "desc"})
|
68
|
+
@controller.sort_direction.should == "desc"
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when the param is invalid' do
|
72
|
+
it 'should return the default sort direction' do
|
73
|
+
@controller.stub!(:params).and_return({})
|
74
|
+
@controller.sort_direction.should == "asc"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SortThis::ActiveRecord do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
setup_db
|
7
|
+
DatabaseCleaner.strategy = :truncation
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
teardown_db
|
12
|
+
end
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
DatabaseCleaner.start
|
16
|
+
end
|
17
|
+
|
18
|
+
after(:each) do
|
19
|
+
DatabaseCleaner.clean
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should have a sort_this class method' do
|
23
|
+
Quote.should respond_to(:sort_this)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should have a sort class method' do
|
27
|
+
Quote.should respond_to(:sort)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.sort_this' do
|
31
|
+
|
32
|
+
shared_examples_for 'sort_columns_defined' do
|
33
|
+
it 'should set the sort_columns options for the specified sort name' do
|
34
|
+
Quote.sort_columns.should have_key(sort_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'sort_columns for the specified sort name' do
|
38
|
+
it 'should properly set the column_name option' do
|
39
|
+
Quote.sort_columns[sort_name][:column_name].should == column_name_option
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should properly set the default option' do
|
43
|
+
Quote.sort_columns[sort_name][:default].should == default_option
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should properly set the joins option' do
|
47
|
+
Quote.sort_columns[sort_name][:joins].should == joins_option
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should properly set the clause option' do
|
51
|
+
Quote.sort_columns[sort_name][:clause].should == clause_option
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'given no parameters' do
|
57
|
+
before(:each) do
|
58
|
+
Quote.sort_this()
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should set sort_columns to an empty hash' do
|
62
|
+
Quote.sort_columns.should == {}
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should set default_sort_columns to an empty hash' do
|
66
|
+
Quote.default_sort_columns.should == {}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'given a sort column with just the required sort options' do
|
71
|
+
let!(:sort_name) { :price }
|
72
|
+
let!(:column_name_option) { :price }
|
73
|
+
let!(:default_option) { nil }
|
74
|
+
let!(:joins_option) { nil }
|
75
|
+
let!(:clause_option) { "quotes.price" }
|
76
|
+
|
77
|
+
before(:each) do
|
78
|
+
Quote.sort_this sort_name => {:column_name => column_name_option}
|
79
|
+
end
|
80
|
+
|
81
|
+
it_should_behave_like 'sort_columns_defined'
|
82
|
+
|
83
|
+
it 'should set default_sort_columns to an empty hash' do
|
84
|
+
Quote.default_sort_columns.should == {}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'given a sort column with a default specified in the sort options' do
|
89
|
+
let!(:sort_name) { :price }
|
90
|
+
let!(:column_name_option) { :price }
|
91
|
+
let!(:default_option) { 'DESC' }
|
92
|
+
let!(:joins_option) { nil }
|
93
|
+
let!(:clause_option) { "quotes.price" }
|
94
|
+
|
95
|
+
before(:each) do
|
96
|
+
Quote.sort_this sort_name => {:column_name => column_name_option, :default => default_option}
|
97
|
+
end
|
98
|
+
|
99
|
+
it_should_behave_like 'sort_columns_defined'
|
100
|
+
|
101
|
+
it 'should set the default_sort_columns options for the specified sort name' do
|
102
|
+
Quote.default_sort_columns.should have_key(sort_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should set the clause of the default_sort_columns for the specified sort name' do
|
106
|
+
Quote.default_sort_columns[sort_name].should == "#{clause_option} #{default_option}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'given a sort column with a joins specified in the sort options' do
|
111
|
+
let!(:sort_name) { :product_name }
|
112
|
+
let!(:column_name_option) { :name }
|
113
|
+
let!(:default_option) { nil }
|
114
|
+
let!(:joins_option) { :product }
|
115
|
+
let!(:clause_option) { "products.name" }
|
116
|
+
|
117
|
+
before(:each) do
|
118
|
+
Quote.sort_this sort_name => {:column_name => column_name_option, :joins => joins_option}
|
119
|
+
end
|
120
|
+
|
121
|
+
it_should_behave_like 'sort_columns_defined'
|
122
|
+
|
123
|
+
it 'should set default_sort_columns to an empty hash' do
|
124
|
+
Quote.default_sort_columns.should == {}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'given a sort column with a custom clause specified in the sort options' do
|
129
|
+
let!(:sort_name) { :price }
|
130
|
+
let!(:column_name_option) { :price }
|
131
|
+
let!(:default_option) { nil }
|
132
|
+
let!(:joins_option) { nil }
|
133
|
+
let!(:clause_option) { "custom.joins_clause" }
|
134
|
+
|
135
|
+
before(:each) do
|
136
|
+
Quote.sort_this sort_name => {:column_name => column_name_option, :clause => clause_option}
|
137
|
+
end
|
138
|
+
|
139
|
+
it_should_behave_like 'sort_columns_defined'
|
140
|
+
|
141
|
+
it 'should set default_sort_columns to an empty hash' do
|
142
|
+
Quote.default_sort_columns.should == {}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'given a sort column with a custom clause and default specified in the sort options' do
|
147
|
+
let!(:sort_name) { :quantity }
|
148
|
+
let!(:column_name_option) { :quantity }
|
149
|
+
let!(:default_option) { 'DESC' }
|
150
|
+
let!(:joins_option) { nil }
|
151
|
+
let!(:clause_option) { "custom.joins_clause" }
|
152
|
+
|
153
|
+
before(:each) do
|
154
|
+
Quote.sort_this sort_name => {:column_name => column_name_option, :default => default_option, :clause => clause_option}
|
155
|
+
end
|
156
|
+
|
157
|
+
it_should_behave_like 'sort_columns_defined'
|
158
|
+
|
159
|
+
it 'should set the default_sort_columns options for the specified sort name' do
|
160
|
+
Quote.default_sort_columns.should have_key(sort_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should set the clause of the default_sort_columns for the specified sort name' do
|
164
|
+
Quote.default_sort_columns[sort_name].should == "#{clause_option} #{default_option}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '.sort' do
|
170
|
+
let!(:product1) { create(:product, :name => 'D Name') }
|
171
|
+
let!(:product2) { create(:product, :name => 'B Name') }
|
172
|
+
let!(:product3) { create(:product, :name => 'A Name') }
|
173
|
+
let!(:product4) { create(:product, :name => 'C Name') }
|
174
|
+
|
175
|
+
let!(:vendor1) { create(:vendor, :name => 'D Name') }
|
176
|
+
let!(:vendor2) { create(:vendor, :name => 'B Name') }
|
177
|
+
let!(:vendor3) { create(:vendor, :name => 'A Name') }
|
178
|
+
let!(:vendor4) { create(:vendor, :name => 'C Name') }
|
179
|
+
|
180
|
+
let!(:quote1) { create(:quote, :product => product1, :vendor => vendor4, :price => 10.55, :quantity => 4) }
|
181
|
+
let!(:quote2) { create(:quote, :product => product3, :vendor => vendor2, :price => 9.31, :quantity => 2) }
|
182
|
+
let!(:quote3) { create(:quote, :product => product2, :vendor => vendor1, :price => 8.25, :quantity => 10) }
|
183
|
+
let!(:quote4) { create(:quote, :product => product4, :vendor => vendor3, :price => 15.12, :quantity => 8) }
|
184
|
+
|
185
|
+
before(:each) do
|
186
|
+
Quote.sort_this :price => {:column_name => :price, :default => 'ASC'},
|
187
|
+
:quantity => {:column_name => :quantity},
|
188
|
+
:product_name => {:column_name => :name, :joins => :product},
|
189
|
+
:vendor_name => {:column_name => :name, :joins => :vendor}
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'default parameters' do
|
193
|
+
it 'should sort by quote price ascending' do
|
194
|
+
Quote.sort.should == [quote3, quote2, quote1, quote4]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'given a sort column' do
|
199
|
+
context 'without specifying an order' do
|
200
|
+
it 'should sort by the specified column in ascending order' do
|
201
|
+
Quote.sort(:quantity).should == [quote2, quote1, quote4, quote3]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'specifying an order' do
|
206
|
+
it 'should sort by the specified column in descending order' do
|
207
|
+
Quote.sort(:quantity, 'DESC').should == [quote3, quote4, quote1, quote2]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'given a sort column from another table' do
|
213
|
+
it 'should sort by the specified column in descending order' do
|
214
|
+
Quote.sort(:product_name, 'DESC').should == [quote1, quote4, quote3, quote2]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
|
3
|
+
factory :product do
|
4
|
+
sequence(:name) { |n| "ProductName#{n}" }
|
5
|
+
end
|
6
|
+
|
7
|
+
factory :vendor do
|
8
|
+
sequence(:name) { |n| "VendorName#{n}" }
|
9
|
+
end
|
10
|
+
|
11
|
+
factory :quote do
|
12
|
+
product
|
13
|
+
vendor
|
14
|
+
price 10.99
|
15
|
+
quantity 10
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'debugger'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'active_record'
|
4
|
+
require 'factory_girl'
|
5
|
+
require 'database_cleaner'
|
6
|
+
require 'capybara'
|
7
|
+
require 'rspec'
|
8
|
+
|
9
|
+
require 'sort_this'
|
10
|
+
|
11
|
+
Dir[File.expand_path("support/**/*.rb", File.dirname(__FILE__))].each {|f| require f}
|
12
|
+
|
13
|
+
FactoryGirl.find_definitions
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.include FactoryGirl::Syntax::Methods
|
17
|
+
end
|
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
def setup_db
|
2
|
+
puts "\nSetting up the database...\n"
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
5
|
+
|
6
|
+
ActiveRecord::Schema.define(:version => 1) do
|
7
|
+
create_table("products") do |t|
|
8
|
+
t.string "name"
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table("quotes") do |t|
|
12
|
+
t.integer "product_id"
|
13
|
+
t.integer "vendor_id"
|
14
|
+
t.decimal "price", :precision => 10, :scale => 2, :default => 0.0
|
15
|
+
t.integer "quantity", :default => 0
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table("vendors") do |t|
|
19
|
+
t.string "name"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown_db
|
25
|
+
puts "\nTearing down the database...\n"
|
26
|
+
|
27
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
28
|
+
ActiveRecord::Base.connection.drop_table(table)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Product < ActiveRecord::Base
|
33
|
+
has_many :quotes
|
34
|
+
end
|
35
|
+
|
36
|
+
class Vendor < ActiveRecord::Base
|
37
|
+
has_many :quotes
|
38
|
+
end
|
39
|
+
|
40
|
+
class Quote < ActiveRecord::Base
|
41
|
+
include SortThis::ActiveRecord
|
42
|
+
|
43
|
+
belongs_to :product
|
44
|
+
belongs_to :vendor
|
45
|
+
|
46
|
+
#sortable :price => {:column_name => :price, :default => 'ASC'},
|
47
|
+
# :quantity => {:column_name => :quantity},
|
48
|
+
# :product_name => {:column_name => :name, :joins => :product},
|
49
|
+
# :vendor_name => {:column_name => :name, :joins => :vendor}
|
50
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# there's probably a better way to test this
|
4
|
+
# check out https://github.com/mislav/will_paginate/blob/master/spec/view_helpers/action_view_spec.rb#L28
|
5
|
+
|
6
|
+
class TestActionView < ActionView::Base
|
7
|
+
include SortThis::ViewHelpers::ActionView
|
8
|
+
end
|
9
|
+
|
10
|
+
describe SortThis::ViewHelpers::ActionView do
|
11
|
+
|
12
|
+
let!(:params_base) { {:controller => 'quotes', :action => 'index'} }
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@helper = TestActionView.new
|
16
|
+
@helper.stub!(:url_for).and_return('/quotes/index')
|
17
|
+
@helper.stub!(:params).and_return(params_base)
|
18
|
+
@helper.stub!(:sort_column).and_return("price")
|
19
|
+
@helper.stub!(:sort_direction).and_return("asc")
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#sortable' do
|
23
|
+
context 'given the column is the only specified parameter' do
|
24
|
+
before(:each) do
|
25
|
+
@node = Capybara::Node::Simple.new(@helper.sortable("test"))
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should set the title to be the capitalized version of the column name' do
|
29
|
+
@node.should have_content("Test")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'given the sort column is the current column being sorted' do
|
34
|
+
before(:each) do
|
35
|
+
@node = Capybara::Node::Simple.new(@helper.sortable("price"))
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should set the sortable-current class' do
|
39
|
+
@node.should have_css('a.sortable-current')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should set the sortable-<direction>' do
|
43
|
+
@node.should have_css('a.sortable-asc')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'given the sort column is not the current column being sorted' do
|
48
|
+
before(:each) do
|
49
|
+
@node = Capybara::Node::Simple.new(@helper.sortable("other_column"))
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should set the sortable-current class' do
|
53
|
+
@node.should_not have_css('a.sortable-current')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should set the sortable-<direction>' do
|
57
|
+
@node.should_not have_css('a.sortable-asc')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'given the title is specified' do
|
62
|
+
before(:each) do
|
63
|
+
@node = Capybara::Node::Simple.new(@helper.sortable("column", "Custom Title"))
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should set the sortable-current class' do
|
67
|
+
@node.should have_content("Custom Title")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sort_this
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Scott Pullen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: memoist
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.2.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.2.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: debugger
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.2
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.2
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.12.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - '='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.12.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: sqlite3
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.3.6
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - '='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.3.6
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: factory_girl
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 4.1.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 4.1.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: database_cleaner
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.9.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - '='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.9.1
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: capybara
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - '='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 2.0.1
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - '='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 2.0.1
|
142
|
+
description: SortThis
|
143
|
+
email:
|
144
|
+
- s.pullen05@gmail.com
|
145
|
+
executables: []
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- .gitignore
|
150
|
+
- .rspec
|
151
|
+
- .rvmrc
|
152
|
+
- CHANGELOG.md
|
153
|
+
- Gemfile
|
154
|
+
- LICENSE.txt
|
155
|
+
- README.md
|
156
|
+
- Rakefile
|
157
|
+
- lib/sort_this.rb
|
158
|
+
- lib/sort_this/action_controller.rb
|
159
|
+
- lib/sort_this/active_record.rb
|
160
|
+
- lib/sort_this/railtie.rb
|
161
|
+
- lib/sort_this/version.rb
|
162
|
+
- lib/sort_this/view_helpers/action_view.rb
|
163
|
+
- sort_this.gemspec
|
164
|
+
- spec/action_controller_spec.rb
|
165
|
+
- spec/active_record_spec.rb
|
166
|
+
- spec/factories.rb
|
167
|
+
- spec/spec_helper.rb
|
168
|
+
- spec/support/.gitkeep
|
169
|
+
- spec/support/models.rb
|
170
|
+
- spec/view_helpers/action_view_spec.rb
|
171
|
+
homepage: https://github.com/spullen/sort_this
|
172
|
+
licenses: []
|
173
|
+
post_install_message:
|
174
|
+
rdoc_options: []
|
175
|
+
require_paths:
|
176
|
+
- lib
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
none: false
|
179
|
+
requirements:
|
180
|
+
- - ! '>='
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
segments:
|
184
|
+
- 0
|
185
|
+
hash: 2103404262135696406
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
187
|
+
none: false
|
188
|
+
requirements:
|
189
|
+
- - ! '>='
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0'
|
192
|
+
segments:
|
193
|
+
- 0
|
194
|
+
hash: 2103404262135696406
|
195
|
+
requirements: []
|
196
|
+
rubyforge_project:
|
197
|
+
rubygems_version: 1.8.24
|
198
|
+
signing_key:
|
199
|
+
specification_version: 3
|
200
|
+
summary: SortThis
|
201
|
+
test_files:
|
202
|
+
- spec/action_controller_spec.rb
|
203
|
+
- spec/active_record_spec.rb
|
204
|
+
- spec/factories.rb
|
205
|
+
- spec/spec_helper.rb
|
206
|
+
- spec/support/.gitkeep
|
207
|
+
- spec/support/models.rb
|
208
|
+
- spec/view_helpers/action_view_spec.rb
|