raymond 0.0.1

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Christoph Petschnig
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,82 @@
1
+ = raymond
2
+
3
+ Gives your view tables nice sorting ability with ease.
4
+
5
+ == Usage
6
+
7
+ Prepare your model: specify, which columns may get sorted.
8
+
9
+ class VeryImportantData < ActiveRecord::Base
10
+
11
+ include Raymond::Model
12
+
13
+ # sort by these db table columns
14
+ allow_sort_by :name, :created_at
15
+
16
+ # sort by this method, that is _not_ a db table column
17
+ allow_sort_by :my_custom_method, :db => false
18
+
19
+ # do a very customized sort
20
+ allow_sort_by :my_sort_key, :method => Proc.new{|obj| obj.my_method(:my_param)}
21
+
22
+ ...
23
+
24
+ Not very much to do in the Controller:
25
+
26
+ class VeryImportantDataController < ApplicationController
27
+
28
+ def index
29
+ # create the sorting object and pass the sorting parameters
30
+ @sorting = Raymond::Control.new(self, params[:sort])
31
+
32
+ # fetch the ordered result from the db; do method based sorting if necessary.
33
+ @very_important_data = @sorting.result
34
+
35
+ respond_to do |format|
36
+ format.html # index.html.erb
37
+ end
38
+ end
39
+
40
+ ...
41
+
42
+ Use a helper method in your view to show the table headers:
43
+
44
+ <tr>
45
+ <th><%= sort_header :name, 'Name' %></th>
46
+ <th><%= sort_header :my_custom_method, 'Column #2' %></th>
47
+ <th><%= sort_header :my_sort_key, 'Column #3' %></th>
48
+ <th><%= sort_header :created_at, 'Since' %></th>
49
+ </tr>
50
+
51
+ And the helper method (not included in Gem):
52
+
53
+ def sort_header(key, label)
54
+ current = @sorting.current_attr?(key)
55
+ symbol = current ? (@sorting.dir == :up ? '▲' : '▼') : ''
56
+ link_to "#{symbol}#{label}", "?sort=#{key}-#{current ? @sorting.inverse_dir : Raymond::DEFAULT_SORTING_DIR.to_s}"
57
+ end
58
+
59
+ == More Customization
60
+
61
+ @sorting = Raymond::Control.new(self, params[:sort])
62
+
63
+ In the example above, the class to query it determined by the name of the controller
64
+ applying Ruby on Rails conventions (PeopleController will use the class Person).
65
+ Passing the option :class will allow you to use any other class:
66
+
67
+ @sorting = Raymond::Control.new(self, params[:sort], :class => PlzUseThisClass)
68
+
69
+ Often, you may want more control of the underlying SQL statement. Thanks to Arel,
70
+ anything is possible:
71
+
72
+ @comments = @sorting.result do |active_relation|
73
+ active_relation.where(:deleted => false).limit(20)
74
+ end
75
+
76
+ == What's next?
77
+
78
+ There's more to come, this is only version 0.0.1.
79
+
80
+ == Copyright
81
+
82
+ Copyright (c) 2010 Christoph Petschnig. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "raymond"
8
+ gem.summary = %Q{Sort columns in tables}
9
+ gem.description = %Q{Enhance your views with sortable tables}
10
+ gem.email = "info@purevirtual.de"
11
+ gem.homepage = "http://github.com/cpetschnig/raymond"
12
+ gem.authors = ["Christoph Petschnig"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ begin
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new
41
+ rescue LoadError
42
+ task :yardoc do
43
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
44
+ end
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/raymond.rb ADDED
@@ -0,0 +1,26 @@
1
+ #encoding: UTF-8
2
+
3
+ #module SortHelper
4
+ #
5
+ # def sort_header(key, label)
6
+ # current = @sorterer.current_attr?(key)
7
+ # symbol = current ? "#{Raymond::SORTING_SYMBOLS[@sorterer.dir]} " : ''
8
+ # link_to "#{symbol}#{label}", "?sort=#{key}-#{current ? @sorterer.inverse_dir : Raymond::DEFAULT_SORTING_DIR.to_s}"
9
+ # end
10
+ #
11
+ #end
12
+
13
+ module Raymond
14
+
15
+ SORTING_DIRECTIONS = {:up => 'ASC', :down => 'DESC'}
16
+
17
+ # ⇓ ⇑ ➷ ➹ ➴ ➶ ▼ ▲
18
+ SORTING_SYMBOLS = {:up => '▲', :down => '▼'}
19
+
20
+ DEFAULT_SORTING_DIR = :down
21
+
22
+ end
23
+
24
+
25
+ require 'raymond/control'
26
+ require 'raymond/model'
@@ -0,0 +1,90 @@
1
+ module Raymond
2
+
3
+ class Control
4
+
5
+ attr_reader :dir, :attr
6
+
7
+ # Constructor
8
+ #
9
+ # @param [Object] controller is used to figure out the sort class; if option[:class] is
10
+ # given, the value is ignored.
11
+ # @param [String, Array] sort_params comes in the string form of "my_attribute-down" or
12
+ # "other_attribute-up", or in Array form of [:attribute, :direction]
13
+ # @param [Hash, #[]] options:
14
+ # +:class+: specify which class to sort; if omitted, will try to build out
15
+ # of the name of controller. The class must include Raymond::Model
16
+ # +:logger+: the logger to use; defaults to the controllers logger or +Rails.logger+
17
+ def initialize(controller, sort_params, options = {})
18
+ @attr, @dir = if sort_params.kind_of?(String)
19
+ (sort_params || '').split('-').map(&:to_sym)
20
+ else
21
+ raise ArgumentError.new("sort_params must be either String or Array.") unless sort_params.kind_of?(Array)
22
+ raise ArgumentError.new("As as Array, sort_params must be in the form of [:my_attribute, :up_or_down].") unless sort_params.size == 2
23
+ sort_params.map(&:to_sym)
24
+ end
25
+
26
+ @class = if (klass = options[:class])
27
+ if klass.kind_of?(Class)
28
+ klass
29
+ else
30
+ klass.to_s.constantize # ActiveSupport dependency
31
+ end
32
+ else
33
+ # class was not explicitly specified; we assume it's related to the name controller name
34
+ controller.class.name[0..-11].singularize.constantize # 'Controller' is 11 characters long; ActiveSupport dependency!
35
+ #
36
+ # achieve the same without ActiveSupport:
37
+ #@class = Module.const_get(controller.class.name[0..-11].singularize) # 'Controller' is 11 characters long
38
+ end
39
+
40
+ # try to use the controllers logger, if available
41
+ @logger = options[:logger] || (controller.respond_to?(:logger) && controller.logger)
42
+
43
+ self.logger.warn "Trying to sort by `#{@attr}`, which is not allowed for class #{@class}." unless attr_valid?
44
+ end
45
+
46
+ # Returns the sorted result
47
+ # When a block is given, you can hook in between SQL sorting and Ruby sorting.
48
+ #
49
+ # Example:
50
+ # @sorting = Raymond::Control.new(self, params[:sort], :class => MyFaboulousModel)
51
+ # @retrieved_data = @sorting.result do |arel|
52
+ # arel.limit(20).offset(120).where(:foo => 'bar')
53
+ # end
54
+ #
55
+ # @return [Array] the results in the requested sort order
56
+ def result
57
+ result = @class.scoped
58
+
59
+ sql_order = @class.sql_sort_order(@attr, @dir)
60
+ result = result.order(sql_order) unless sql_order.blank?
61
+
62
+ result = yield(result) if block_given?
63
+
64
+ sort_proc = @class.sort_proc(@attr)
65
+ result = result.sort_by{|obj| sort_proc.call(obj)} if sort_proc
66
+
67
+ return result.reverse if sort_proc && @dir == :up
68
+
69
+ result
70
+ end
71
+
72
+ def inverse_dir
73
+ SORTING_DIRECTIONS.keys.detect{|dir| dir != @dir}
74
+ end
75
+
76
+ def attr_valid?
77
+ @class.sort_attributes.include?(@attr)
78
+ end
79
+
80
+ def current_attr?(attr)
81
+ attr.to_sym == @attr && self.attr_valid?
82
+ end
83
+
84
+ def logger #:nodoc:
85
+ @logger ||= (Module.const_defined?('Rails') && Rails.logger)
86
+ end
87
+
88
+ end
89
+ end
90
+
@@ -0,0 +1,66 @@
1
+ module Raymond
2
+
3
+ module Model
4
+
5
+ def self.included(base)
6
+ # Class enhancements
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ # the methods in this module will become class methods
11
+ module ClassMethods
12
+
13
+ # Define all columns or attributes, that may be sorted upon, by this method
14
+ # @param [Array, Hash] params can be
15
+ # an array of column names
16
+ # a single parameter with a Hash of options
17
+ def allow_sort_by(*params)
18
+ @sort_attributes ||= []
19
+
20
+ # give the use a little bit more freedom by allowing to pass an array of column names
21
+ params = params.first if params.size == 1 && params.first.kind_of?(Array)
22
+
23
+ options = params.pop if params.last.kind_of?(Hash)
24
+
25
+ raise ArgumentError.new('Method allow_sort_by takes either an array of column names or' +
26
+ 'a single column name with a optional hash of options.') if params.size != 1 && options
27
+
28
+ params = params.map(&:to_sym)
29
+
30
+ params = [{params.first => options}] if options
31
+
32
+ # the items of params must all be column names, if params are symbols
33
+
34
+ @sort_attributes += params
35
+ end
36
+
37
+ def sort_attributes
38
+ raise "Trying to sort with no sort attributes defined." unless @sort_attributes
39
+ @sort_attributes.map{|attr| attr.kind_of?(Symbol) ? attr : attr.keys.first}
40
+ end
41
+
42
+ def sort_proc(attr)#, direction)
43
+ hash = @sort_attributes.detect{|obj| obj.kind_of?(Hash) && obj[attr]}
44
+ method_result = hash && hash[attr] && hash[attr][:method]
45
+ return method_result if method_result
46
+ hash && hash[attr] && hash[attr][:db] == false && Proc.new{|obj| obj.send(attr)}
47
+ end
48
+
49
+ def sql_sort_order(attr, direction)
50
+ second = @secondary_sort_attr ? "#{@secondary_sort_attr} #{SORTING_DIRECTIONS[@secondary_sort_direction]}" : nil
51
+ first = (@sort_attributes || []).include?(attr) ? "#{attr} #{SORTING_DIRECTIONS[direction]}" : nil
52
+ [first, second].compact.join(', ')
53
+ end
54
+
55
+ def secondary_sort_attr(attr, direction = :up)
56
+ raise ArgumentError.new("First argument of secondary_sort_attr must be a column name.") unless self.column_names.include?(attr.to_s)
57
+ raise ArgumentError.new("Second argument of secondary_sort_attr must be one of #{SORTING_DIRECTIONS.keys.map{|key| ":#{key}"}}.") unless SORTING_DIRECTIONS.keys.include?(direction)
58
+ @secondary_sort_attr = attr
59
+ @secondary_sort_direction = direction
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
data/raymond.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{raymond}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christoph Petschnig"]
12
+ s.date = %q{2010-11-09}
13
+ s.description = %q{Enhance your views with sortable tables}
14
+ s.email = %q{info@purevirtual.de}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/raymond.rb",
27
+ "lib/raymond/control.rb",
28
+ "lib/raymond/model.rb",
29
+ "raymond.gemspec",
30
+ "spec/raymond_spec.rb",
31
+ "spec/spec.opts",
32
+ "spec/spec_helper.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/cpetschnig/raymond}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.7}
38
+ s.summary = %q{Sort columns in tables}
39
+ s.test_files = [
40
+ "spec/raymond_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
50
+ s.add_development_dependency(%q<yard>, [">= 0"])
51
+ else
52
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
53
+ s.add_dependency(%q<yard>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
57
+ s.add_dependency(%q<yard>, [">= 0"])
58
+ end
59
+ end
60
+
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class MySpecController
4
+ include Raymond::Control
5
+ def index(params)
6
+ @sorting = Raymond::Control.new(self, params[:sort])
7
+ @result_array = @sorting.result
8
+ end
9
+ end
10
+
11
+ class MySpecModel
12
+ include Raymond::Model
13
+
14
+ allow_sort_by :start_at, :title, :km
15
+ allow_sort_by :duration, :method => Proc.new{|obj| obj.duration(:numeric)}
16
+ allow_sort_by :modality, :method => Proc.new{|obj| obj.modality && obj.modality.name}
17
+ allow_sort_by :costs, :db => false
18
+
19
+ secondary_sort_attr :start_at
20
+
21
+
22
+ end
23
+
24
+ describe "Raymond" do
25
+ it "fails" do
26
+ fail "hey buddy, you should probably rename this file and start specing for real"
27
+ end
28
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'raymond'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: raymond
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Christoph Petschnig
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-09 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 9
34
+ version: 1.2.9
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: yard
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ description: Enhance your views with sortable tables
52
+ email: info@purevirtual.de
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files:
58
+ - LICENSE
59
+ - README.rdoc
60
+ files:
61
+ - .document
62
+ - .gitignore
63
+ - LICENSE
64
+ - README.rdoc
65
+ - Rakefile
66
+ - VERSION
67
+ - lib/raymond.rb
68
+ - lib/raymond/control.rb
69
+ - lib/raymond/model.rb
70
+ - raymond.gemspec
71
+ - spec/raymond_spec.rb
72
+ - spec/spec.opts
73
+ - spec/spec_helper.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/cpetschnig/raymond
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --charset=UTF-8
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.3.7
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Sort columns in tables
108
+ test_files:
109
+ - spec/raymond_spec.rb
110
+ - spec/spec_helper.rb