pacecar 1.4.3 → 1.4.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pacecar.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'pacecar/associations'
2
+ require 'pacecar/boolean'
3
+ require 'pacecar/datetime'
4
+ require 'pacecar/duration'
5
+ require 'pacecar/helpers'
6
+ require 'pacecar/limit'
7
+ require 'pacecar/order'
8
+ require 'pacecar/polymorph'
9
+ require 'pacecar/presence'
10
+ require 'pacecar/ranking'
11
+ require 'pacecar/search'
12
+ require 'pacecar/state'
13
+ require 'pacecar/numeric'
14
+
15
+ module Pacecar
16
+ def self.included(base)
17
+ base.class_eval do
18
+ include Pacecar::Associations
19
+ include Pacecar::Boolean
20
+ include Pacecar::Datetime
21
+ include Pacecar::Duration
22
+ include Pacecar::Limit
23
+ include Pacecar::Order
24
+ include Pacecar::Polymorph
25
+ include Pacecar::Presence
26
+ include Pacecar::Ranking
27
+ include Pacecar::Search
28
+ include Pacecar::State
29
+ include Pacecar::Numeric
30
+ end
31
+ end
32
+ end
33
+
34
+ ActiveRecord::Base.send :include, Pacecar::Helpers
@@ -0,0 +1,39 @@
1
+ module Pacecar
2
+ module Associations
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def has_recent_records(*names)
10
+ names.each do |name|
11
+ scope "recent_#{name}_since".to_sym, lambda { |since|
12
+ {
13
+ :conditions => [conditions_for_name(name), { :since_time => since }]
14
+ }
15
+ }
16
+ end
17
+ unless names.first == names.last
18
+ scope "recent_#{names.join('_or_')}_since".to_sym, lambda { |since|
19
+ {
20
+ :conditions => [names.collect { |name| conditions_for_name(name) }.join(' or '), { :since_time => since }]
21
+ }
22
+ }
23
+ scope "recent_#{names.join('_and_')}_since".to_sym, lambda { |since|
24
+ {
25
+ :conditions => [names.collect { |name| conditions_for_name(name) }.join(' and '), { :since_time => since }]
26
+ }
27
+ }
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def conditions_for_name(name)
34
+ "((select count(*) from #{connection.quote_table_name(name)} where #{connection.quote_table_name(name)}.#{connection.quote_column_name reflections[name].primary_key_name} = #{quoted_table_name}.#{connection.quote_column_name primary_key} and #{connection.quote_table_name(name)}.#{connection.quote_column_name("created_at")} > :since_time) > 0)"
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ module Pacecar
2
+ module Boolean
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_boolean_scopes
10
+ base.send :define_balance_count
11
+ end
12
+
13
+ protected
14
+
15
+ def define_boolean_scopes
16
+ boolean_column_names.each do |name|
17
+ scope name.to_sym, :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} = ?", true]
18
+ scope "not_#{name}".to_sym, :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} = ?", false]
19
+ end
20
+ end
21
+
22
+ def define_balance_count
23
+ boolean_column_names.each do |name|
24
+ self.class_eval %Q{
25
+ def self.#{name}_balance
26
+ #{name}.count - not_#{name}.count
27
+ end
28
+ }
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,91 @@
1
+ module Pacecar
2
+ module Datetime
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_datetime_scopes
10
+ end
11
+
12
+ protected
13
+
14
+ def define_datetime_scopes
15
+ datetime_column_names.each do |name|
16
+ define_before_after_scopes(name)
17
+ define_past_future_scopes(name)
18
+ define_inside_outside_scopes(name)
19
+ define_in_date_scopes(name)
20
+ end
21
+ end
22
+
23
+ def define_before_after_scopes(name)
24
+ scope "#{name}_before".to_sym, lambda { |time|
25
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} <= ?", time] }
26
+ }
27
+ scope "#{name}_after".to_sym, lambda { |time|
28
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} >= ?", time] }
29
+ }
30
+ end
31
+
32
+ def define_past_future_scopes(name)
33
+ scope "#{name}_in_past", lambda {
34
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} <= ?", now] }
35
+ }
36
+ scope "#{name}_in_future", lambda {
37
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} >= ?", now] }
38
+ }
39
+ end
40
+
41
+ def define_inside_outside_scopes(name)
42
+ scope "#{name}_inside".to_sym, lambda { |start, stop|
43
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} >= ? and #{quoted_table_name}.#{connection.quote_column_name name} <= ?", start, stop] }
44
+ }
45
+ scope "#{name}_outside".to_sym, lambda { |start, stop|
46
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} <= ? or #{quoted_table_name}.#{connection.quote_column_name name} >= ?", start, stop] }
47
+ }
48
+ end
49
+
50
+ def define_in_date_scopes(name)
51
+ case connection.adapter_name
52
+ when 'MySQL', 'Mysql2'
53
+ scope "#{name}_in_year".to_sym, lambda { |year|
54
+ { :conditions => ["year(#{quoted_table_name}.#{connection.quote_column_name name}) = ?", year.to_i] }
55
+ }
56
+ scope "#{name}_in_month".to_sym, lambda { |month|
57
+ { :conditions => ["month(#{quoted_table_name}.#{connection.quote_column_name name}) = ?", month.to_i] }
58
+ }
59
+ scope "#{name}_in_day".to_sym, lambda { |day|
60
+ { :conditions => ["day(#{quoted_table_name}.#{connection.quote_column_name name}) = ?", day.to_i] }
61
+ }
62
+ when 'PostgreSQL'
63
+ scope "#{name}_in_year".to_sym, lambda { |year|
64
+ { :conditions => ["extract(year from #{quoted_table_name}.#{connection.quote_column_name name}) = ?", year.to_i] }
65
+ }
66
+ scope "#{name}_in_month".to_sym, lambda { |month|
67
+ { :conditions => ["extract(month from #{quoted_table_name}.#{connection.quote_column_name name}) = ?", month.to_i] }
68
+ }
69
+ scope "#{name}_in_day".to_sym, lambda { |day|
70
+ { :conditions => ["extract(day from #{quoted_table_name}.#{connection.quote_column_name name}) = ?", day.to_i] }
71
+ }
72
+ when 'SQLite'
73
+ scope "#{name}_in_year".to_sym, lambda { |year|
74
+ { :conditions => ["strftime('%Y', #{quoted_table_name}.#{connection.quote_column_name name}) = ?", sprintf('%04d', year)] }
75
+ }
76
+ scope "#{name}_in_month".to_sym, lambda { |month|
77
+ { :conditions => ["strftime('%m', #{quoted_table_name}.#{connection.quote_column_name name}) = ?", sprintf('%02d', month)] }
78
+ }
79
+ scope "#{name}_in_day".to_sym, lambda { |day|
80
+ { :conditions => ["strftime('%d', #{quoted_table_name}.#{connection.quote_column_name name}) = ?", sprintf('%02d', day)] }
81
+ }
82
+ end
83
+ end
84
+
85
+ def now
86
+ defined?(Time.zone_default) && Time.zone_default ? Time.zone_default.now : Time.now
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,51 @@
1
+ module Pacecar
2
+ module Duration
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_duration_scopes
10
+ end
11
+
12
+ protected
13
+
14
+ def define_duration_scopes
15
+ case connection.adapter_name
16
+ when 'MySQL', 'Mysql2'
17
+ scope :with_duration_of, lambda { |duration, start, stop|
18
+ { :conditions => ["abs(datediff(#{quoted_table_name}.#{connection.quote_column_name start}, #{quoted_table_name}.#{connection.quote_column_name stop})) = ?", duration] }
19
+ }
20
+ scope :with_duration_over, lambda { |duration, start, stop|
21
+ { :conditions => ["abs(datediff(#{quoted_table_name}.#{connection.quote_column_name start}, #{quoted_table_name}.#{connection.quote_column_name stop})) > ?", duration] }
22
+ }
23
+ scope :with_duration_under, lambda { |duration, start, stop|
24
+ { :conditions => ["abs(datediff(#{quoted_table_name}.#{connection.quote_column_name start}, #{quoted_table_name}.#{connection.quote_column_name stop})) < ?", duration] }
25
+ }
26
+ when 'PostgreSQL'
27
+ scope :with_duration_of, lambda { |duration, start, stop|
28
+ { :conditions => ["age(#{quoted_table_name}.#{connection.quote_column_name stop}, #{quoted_table_name}.#{connection.quote_column_name start}) = '? days'", duration] }
29
+ }
30
+ scope :with_duration_over, lambda { |duration, start, stop|
31
+ { :conditions => ["age(#{quoted_table_name}.#{connection.quote_column_name stop}, #{quoted_table_name}.#{connection.quote_column_name start}) > interval '? days'", duration] }
32
+ }
33
+ scope :with_duration_under, lambda { |duration, start, stop|
34
+ { :conditions => ["age(#{quoted_table_name}.#{connection.quote_column_name stop}, #{quoted_table_name}.#{connection.quote_column_name start}) < interval '? days'", duration] }
35
+ }
36
+ when 'SQLite'
37
+ scope :with_duration_of, lambda { |duration, start, stop|
38
+ { :conditions => ["abs(julianday(#{quoted_table_name}.#{connection.quote_column_name start}) - julianday(#{quoted_table_name}.#{connection.quote_column_name stop})) = ?", duration] }
39
+ }
40
+ scope :with_duration_over, lambda { |duration, start, stop|
41
+ { :conditions => ["abs(julianday(#{quoted_table_name}.#{connection.quote_column_name start}) - julianday(#{quoted_table_name}.#{connection.quote_column_name stop})) > ?", duration] }
42
+ }
43
+ scope :with_duration_under, lambda { |duration, start, stop|
44
+ { :conditions => ["abs(julianday(#{quoted_table_name}.#{connection.quote_column_name start}) - julianday(#{quoted_table_name}.#{connection.quote_column_name stop})) < ?", duration] }
45
+ }
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,79 @@
1
+ module Pacecar
2
+ module Helpers
3
+
4
+ mattr_accessor :options
5
+ self.options = {
6
+ :state_pattern => /_(type|state)$/i,
7
+ :default_limit => 10
8
+ }
9
+
10
+ def self.included(base)
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ def safe_column_names
17
+ safe_columns.collect(&:name)
18
+ end
19
+
20
+ def non_boolean_column_names
21
+ column_names_without_type :boolean
22
+ end
23
+
24
+ def boolean_column_names
25
+ column_names_for_type :boolean
26
+ end
27
+
28
+ def datetime_column_names
29
+ column_names_for_type :datetime, :date
30
+ end
31
+
32
+ def text_and_string_column_names
33
+ column_names_for_type :text, :string
34
+ end
35
+
36
+ def non_state_text_and_string_columns
37
+ text_and_string_column_names.reject { |name| name =~ Pacecar::Helpers.options[:state_pattern] }
38
+ end
39
+
40
+ def numeric_column_names
41
+ column_names_for_type :integer, :float
42
+ end
43
+
44
+ protected
45
+
46
+ def safe_columns
47
+ case ActiveRecord::Base.connection.adapter_name
48
+ when 'MySQL'
49
+ begin
50
+ columns
51
+ rescue Mysql::Error
52
+ Array.new
53
+ end
54
+ when 'Mysql2'
55
+ begin
56
+ columns
57
+ rescue Mysql2::Error
58
+ Array.new
59
+ end
60
+ when 'SQLite', 'PostgreSQL'
61
+ begin
62
+ columns
63
+ rescue ActiveRecord::StatementInvalid # If the table does not exist
64
+ Array.new
65
+ end
66
+ end
67
+ end
68
+
69
+ def column_names_for_type(*types)
70
+ safe_columns.select { |column| types.include? column.type }.collect(&:name)
71
+ end
72
+
73
+ def column_names_without_type(*types)
74
+ safe_columns.select { |column| ! types.include? column.type }.collect(&:name)
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,22 @@
1
+ module Pacecar
2
+ module Limit
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_limit_scopes
10
+ end
11
+
12
+ protected
13
+
14
+ def define_limit_scopes
15
+ scope :limited, lambda { |*args|
16
+ { :limit => args.flatten.first || (defined?(per_page) ? per_page : Pacecar::Helpers.options[:default_limit]) }
17
+ }
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Pacecar
2
+ module Numeric
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_numeric_scopes
10
+ end
11
+
12
+ protected
13
+
14
+ def define_numeric_scopes
15
+ numeric_column_names.each do |name|
16
+ { :greater_than => '>', :less_than => '<' }.each do |method_name, symbol|
17
+ scope "#{name}_#{method_name}".to_sym, lambda { |value|
18
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} #{symbol} ?", value] }
19
+ }
20
+ scope "#{name}_#{method_name}_or_equal_to".to_sym, lambda { |value|
21
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} #{symbol}= ?", value] }
22
+ }
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Pacecar
2
+ module Order
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_order_scopes
10
+ end
11
+
12
+ protected
13
+
14
+ def define_order_scopes
15
+ safe_column_names.each do |name|
16
+ scope "by_#{name}".to_sym, lambda { |*args|
17
+ { :order => "#{quoted_table_name}.#{connection.quote_column_name name} #{args.flatten.first || 'asc'}" }
18
+ }
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module Pacecar
2
+ module Polymorph
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def has_polymorph(name)
10
+ scope "for_#{name}_type".to_sym, lambda { |type|
11
+ polymorph_type = "#{name}_type"
12
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name polymorph_type} = ?", type.to_s] }
13
+ }
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Pacecar
2
+ module Presence
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_presence_scopes
10
+ end
11
+
12
+ protected
13
+
14
+ def define_presence_scopes
15
+ non_boolean_column_names.each do |name|
16
+ scope "#{name}_present".to_sym, :conditions => "#{quoted_table_name}.#{connection.quote_column_name name} IS NOT NULL"
17
+ scope "#{name}_missing".to_sym, :conditions => "#{quoted_table_name}.#{connection.quote_column_name name} IS NULL"
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ module Pacecar
2
+ module Ranking
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def has_ranking(association)
10
+ define_ranking_scope association, :maximum, :desc
11
+ define_ranking_scope association, :minimum, :asc
12
+ end
13
+
14
+ protected
15
+
16
+ def define_ranking_scope(association, name, direction)
17
+ scope "#{name}_#{association}",
18
+ :select => "#{quoted_table_name}.*, count(#{reflections[association].quoted_table_name}.#{connection.quote_column_name reflections[association].primary_key_name}) as #{association}_count",
19
+ :joins => "inner join #{association} on #{association}.#{reflections[association].primary_key_name} = #{quoted_table_name}.#{connection.quote_column_name primary_key}",
20
+ :group => safe_column_names.collect { |name| "#{quoted_table_name}.#{connection.quote_column_name(name)}" }.join(', '),
21
+ :order => "#{association}_count #{direction}"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ module Pacecar
2
+ module Search
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.send :define_search_scopes
10
+ base.send :define_basic_search_scope
11
+ end
12
+
13
+ protected
14
+
15
+ def define_search_scopes
16
+ safe_column_names.each do |name|
17
+ scope "#{name}_equals".to_sym, lambda { |query|
18
+ { :conditions => { table_name => { name.to_sym => query} } }
19
+ }
20
+ end
21
+ text_and_string_column_names.each do |name|
22
+ scope "#{name}_matches".to_sym, lambda { |query|
23
+ { :conditions => ["lower(#{quoted_table_name}.#{connection.quote_column_name(name)}) LIKE lower(:query)", { :query => "%#{query}%" }] }
24
+ }
25
+ scope "#{name}_starts_with".to_sym, lambda { |query|
26
+ { :conditions => ["lower(#{quoted_table_name}.#{connection.quote_column_name(name)}) LIKE lower(:query)", { :query => "#{query}%" }] }
27
+ }
28
+ scope "#{name}_ends_with".to_sym, lambda { |query|
29
+ { :conditions => ["lower(#{quoted_table_name}.#{connection.quote_column_name(name)}) LIKE lower(:query)", { :query => "%#{query}" }] }
30
+ }
31
+ end
32
+ end
33
+
34
+ def define_basic_search_scope
35
+ scope :search_for, lambda { |*args|
36
+ opts = args.extract_options!
37
+ query = args.flatten.first
38
+ columns = opts[:on] || non_state_text_and_string_columns
39
+ joiner = opts[:require].eql?(:all) ? 'AND' : 'OR'
40
+ match = columns.collect { |name| "lower(#{quoted_table_name}.#{connection.quote_column_name(name)}) LIKE lower(:query)" }.join(" #{joiner} ")
41
+ { :conditions => [match, { :query => "%#{query}%" } ] }
42
+ }
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ module Pacecar
2
+ module State
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def has_state(*names)
10
+ opts = names.extract_options!
11
+ names.each do |name|
12
+ constant = opts[:with] || const_get(name.to_s.pluralize.upcase)
13
+ constant.each do |state|
14
+ scope "#{name}_#{state.downcase}".to_sym, :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} = ?", state]
15
+ scope "#{name}_not_#{state.downcase}".to_sym, :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} <> ?", state]
16
+ self.class_eval %Q{
17
+ def #{name}_#{state.downcase}?
18
+ #{name} == '#{state}'
19
+ end
20
+ def #{name}_not_#{state.downcase}?
21
+ #{name} != '#{state}'
22
+ end
23
+ }
24
+ end
25
+ scope "#{name}".to_sym, lambda { |state|
26
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} = ?", state] }
27
+ }
28
+ scope "#{name}_not".to_sym, lambda { |state|
29
+ { :conditions => ["#{quoted_table_name}.#{connection.quote_column_name name} <> ?", state] }
30
+ }
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pacecar
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 4
9
- - 3
10
- version: 1.4.3
9
+ - 4
10
+ version: 1.4.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matt Jankowski
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-07 00:00:00 -05:00
18
+ date: 2010-12-11 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -31,6 +31,20 @@ files:
31
31
  - init.rb
32
32
  - README.rdoc
33
33
  - MIT-LICENSE
34
+ - lib/pacecar/associations.rb
35
+ - lib/pacecar/boolean.rb
36
+ - lib/pacecar/datetime.rb
37
+ - lib/pacecar/duration.rb
38
+ - lib/pacecar/helpers.rb
39
+ - lib/pacecar/limit.rb
40
+ - lib/pacecar/numeric.rb
41
+ - lib/pacecar/order.rb
42
+ - lib/pacecar/polymorph.rb
43
+ - lib/pacecar/presence.rb
44
+ - lib/pacecar/ranking.rb
45
+ - lib/pacecar/search.rb
46
+ - lib/pacecar/state.rb
47
+ - lib/pacecar.rb
34
48
  has_rdoc: true
35
49
  homepage: http://github.com/thoughtbot/pacecar
36
50
  licenses: []