arel_date_scopes 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ spec/tmp/*/**
2
+ pkg
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ arel_date_scopes (0.0.2)
5
+ activerecord (~> 3.0.0)
6
+ activesupport (~> 3.0.0)
7
+ arel (>= 2)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activemodel (3.0.5)
13
+ activesupport (= 3.0.5)
14
+ builder (~> 2.1.2)
15
+ i18n (~> 0.4)
16
+ activerecord (3.0.5)
17
+ activemodel (= 3.0.5)
18
+ activesupport (= 3.0.5)
19
+ arel (~> 2.0.2)
20
+ tzinfo (~> 0.3.23)
21
+ activesupport (3.0.5)
22
+ arel (2.0.9)
23
+ builder (2.1.2)
24
+ diff-lcs (1.1.2)
25
+ i18n (0.5.0)
26
+ rspec (2.5.0)
27
+ rspec-core (~> 2.5.0)
28
+ rspec-expectations (~> 2.5.0)
29
+ rspec-mocks (~> 2.5.0)
30
+ rspec-core (2.5.1)
31
+ rspec-expectations (2.5.0)
32
+ diff-lcs (~> 1.1.2)
33
+ rspec-mocks (2.5.0)
34
+ sqlite3 (1.3.3)
35
+ sqlite3-ruby (1.3.3)
36
+ sqlite3 (>= 1.3.3)
37
+ tzinfo (0.3.25)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ arel_date_scopes!
44
+ rspec
45
+ sqlite3-ruby
@@ -0,0 +1,51 @@
1
+ = Arel Date Scopes
2
+
3
+ This gem is useful when you want to navigate through your records by years, months or days - for example, at news archive page.
4
+
5
+ * AREL 2 date functions (DATE(), YEAR(), DAYOFMONTH() for MySQL, CAST(STRFTIME(...)) for SQLite)
6
+ * AR 3 scopes.
7
+
8
+ = Installation
9
+
10
+ Put following line in your Gemfile:
11
+
12
+ gem 'arel_date_scopes'
13
+
14
+ = Pure AREL example:
15
+
16
+ Let the users table have created_at field.
17
+
18
+ users = Table[:users]
19
+ users.where(users[:created_at].year.eq(2009))
20
+ users.where(users[:created_at].month.gt(2))
21
+ users.where(users[:created_at].dayofmonth.in(1..20))
22
+
23
+ = AR example:
24
+
25
+ Let the news table have created_at field and contain news for two years: 2008 and 2009.
26
+
27
+ class News < ActiveRecord::Base
28
+ date_scopes_for :created_at # Creates scopes for created_at field.
29
+ end
30
+
31
+ The News model gets the following scopes:
32
+
33
+ * created_at_year_eq
34
+ * created_at_month_eq
35
+ * created_at_day_eq
36
+ * created_at_years
37
+ * created_at_months
38
+ * created_at_days
39
+ * ascend_by_created_at
40
+ * descend_by_created_at
41
+
42
+ Usage example:
43
+
44
+ News.created_at_year_eq(2009).all
45
+ News.created_at_year_eq(2009).created_at_month_eq(5).all
46
+ News.descend_by_created_at.created_at_years.all.first['created_at_year'] # 2009
47
+ News.ascend_by_created_at.created_at_years.all_column # [2008, 2009]
48
+
49
+ = TODO:
50
+
51
+ * Create scopes through method_missing like searchlogic.
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+ require 'arel_date_scopes'
6
+ require 'bundler'
7
+
8
+ Bundler::GemHelper.install_tasks
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "arel_date_scopes/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{arel_date_scopes}
8
+ s.version = ArelDateScopes::VERSION
9
+
10
+ s.authors = ["Victor Sokolov"]
11
+ s.description = %q{SQL date functions for AREL 2 + AR 3 scopes (such as created_at_year_eq)}
12
+ s.email = %q{gzigzigzeo@gmail.com}
13
+ s.homepage = %q{http://github.com/gzigzigzeo/arel_date_scopes}
14
+ s.rdoc_options = ["--charset=UTF-8"]
15
+ s.summary = %q{SQL date functions for AREL 2 + AR 3 date scopes (such as created_at_year_eq)}
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'arel', '>= 2'
23
+ s.add_dependency 'activerecord', '~> 3.0.0'
24
+ s.add_dependency 'activesupport', '~> 3.0.0'
25
+
26
+ s.add_development_dependency('sqlite3-ruby')
27
+ s.add_development_dependency('rspec')
28
+ end
@@ -0,0 +1,6 @@
1
+ module ArelDateScopes
2
+ require 'arel_date_scopes/version'
3
+ require 'arel_date_scopes/arel'
4
+ require 'arel_date_scopes/active_record'
5
+ require 'arel_date_scopes/railtie' if defined?(Rails)
6
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ArelDateScopes
6
+ module ActiveRecord
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def date_scopes_for(field)
11
+ scope :"#{field}_year_eq", lambda { |year|
12
+ t = arel_table
13
+ where(t[field].year.eq(year))
14
+ }
15
+
16
+ scope :"#{field}_month_eq", lambda { |month|
17
+ t = arel_table
18
+ where(t[field].month.eq(month))
19
+ }
20
+
21
+ scope :"#{field}_day_eq", lambda { |day|
22
+ t = arel_table
23
+ where(t[field].dayofmonth.eq(day))
24
+ }
25
+
26
+ scope :"#{field}_years", lambda {
27
+ t = arel_table
28
+ select(t[field].year.as("#{field}_year")).group(t[field].year)
29
+ }
30
+
31
+ scope :"#{field}_months", lambda {
32
+ t = arel_table
33
+ select(t[field].month.as("#{field}_month")).group(t[field].month)
34
+ }
35
+
36
+ scope :"#{field}_days", lambda {
37
+ t = arel_table
38
+ select(t[field].dayofmonth.as("#{field}_day")).group(t[field].dayofmonth)
39
+ }
40
+
41
+ scope :"descend_by_#{field}", order("#{field} DESC")
42
+ scope :"ascend_by_#{field}", order("#{field} ASC")
43
+ end
44
+
45
+ def all_column(field = nil)
46
+ rows = scoped.all
47
+ return [] if rows.empty?
48
+ field ||= rows.first.attributes.keys.first
49
+ rows.map { |row| row[field] }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,83 @@
1
+ require 'arel'
2
+
3
+ module ArelDateScopes
4
+ # Workaround for Rails. Rails tries to threat left operands of == as Attribute object, but it can be
5
+ # a function like YEAR(). Fake #name should return "#{field}_#{function}" to resolve field conflicts.
6
+ module ArelAttributeEmulation
7
+ def name; self.expressions.first.name.to_s + '_' + self.class.name.underscore; end
8
+ def relation; self.expressions.first; end
9
+ end
10
+ end
11
+
12
+ module Arel
13
+ module Nodes
14
+ class Year < Arel::Nodes::Function
15
+ include Arel::Predications
16
+ include ArelDateScopes::ArelAttributeEmulation
17
+ end
18
+
19
+ class Month < Arel::Nodes::Function
20
+ include Arel::Predications
21
+ include ArelDateScopes::ArelAttributeEmulation
22
+ end
23
+
24
+ class DayOfMonth < Arel::Nodes::Function
25
+ include Arel::Predications
26
+ include ArelDateScopes::ArelAttributeEmulation
27
+ end
28
+ end
29
+
30
+ module Attributes
31
+ class Time
32
+ def year
33
+ Nodes::Year.new [self]
34
+ end
35
+
36
+ def month
37
+ Nodes::Month.new [self]
38
+ end
39
+
40
+ def dayofmonth
41
+ Nodes::DayOfMonth.new [self]
42
+ end
43
+ end
44
+ end
45
+
46
+ module Visitors
47
+ class SQLite
48
+ private
49
+ def visit_Arel_Nodes_Year o
50
+ "CAST(STRFTIME('%Y', #{o.expressions.map { |x|
51
+ visit x }.join(', ')}) AS INTEGER)#{o.alias ? " AS #{visit o.alias}" : ''}"
52
+ end
53
+
54
+ def visit_Arel_Nodes_Month o
55
+ "CAST(STRFTIME('%m', #{o.expressions.map { |x|
56
+ visit x }.join(', ')}) AS INTEGER)#{o.alias ? " AS #{visit o.alias}" : ''}"
57
+ end
58
+
59
+ def visit_Arel_Nodes_DayOfMonth o
60
+ "CAST(STRFTIME('%d', #{o.expressions.map { |x|
61
+ visit x }.join(', ')}) AS INTEGER)#{o.alias ? " AS #{visit o.alias}" : ''}"
62
+ end
63
+ end
64
+
65
+ class MySQL
66
+ private
67
+ def visit_Arel_Nodes_Year o
68
+ "YEAR(#{o.expressions.map { |x|
69
+ visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
70
+ end
71
+
72
+ def visit_Arel_Nodes_Month o
73
+ "MONTH(#{o.expressions.map { |x|
74
+ visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
75
+ end
76
+
77
+ def visit_Arel_Nodes_DayOfMonth o
78
+ "DAYOFMONTH(#{o.expressions.map { |x|
79
+ visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ module ArelDateScopes
2
+ class Railtie < Rails::Railtie
3
+ initializer 'arel_date_scopes.insert_into_active_record' do
4
+ ActiveSupport.on_load :active_record do
5
+ ::ActiveRecord::Base.send(:include, ArelDateScopes::ActiveRecord)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module ArelDateScopes
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe "date_scopes AREL extensions" do
4
+ before(:each) { @table = Arel::Table.new(:users) }
5
+
6
+ context("for MySQL") do
7
+ before(:each) do
8
+ @visitor = Arel::Visitors::MySQL.new(Arel::Sql::Engine.new(FakeRecord::Base.new('mysql')))
9
+ end
10
+
11
+ it "should correctly transform to_sql DATE, MONTH, DAYOFMONTH" do
12
+ @visitor.accept(@table[:created_at].year.eq(2009)).should eq('YEAR("users"."created_at") = 2009')
13
+ @visitor.accept(@table[:created_at].month.in(1..12)).should eq('MONTH("users"."created_at") BETWEEN 1 AND 12')
14
+ @visitor.accept(@table[:created_at].dayofmonth.gt(10)).should eq('DAYOFMONTH("users"."created_at") > 10')
15
+ @table.project(@table[:created_at].year).group(@table[:created_at].year).to_sql.should
16
+ eq('SELECT YEAR("users"."created_at") FROM "users" GROUP BY YEAR("users"."created_at")')
17
+ end
18
+ end
19
+
20
+ context("for SQLite") do
21
+ before(:each) do
22
+ @visitor = Arel::Visitors::SQLite.new(Arel::Sql::Engine.new(FakeRecord::Base.new('sqlite')))
23
+ end
24
+
25
+ it "should correctly transform to_sql DATE, MONTH, DAYOFMONTH" do
26
+ @visitor.accept(@table[:created_at].year.eq(2009)).should eq('CAST(STRFTIME(\'%Y\', "users"."created_at") AS INTEGER) = 2009')
27
+ @visitor.accept(@table[:created_at].month.in(1..12)).should eq('CAST(STRFTIME(\'%m\', "users"."created_at") AS INTEGER) BETWEEN 1 AND 12')
28
+ @visitor.accept(@table[:created_at].dayofmonth.gt(10)).should eq('CAST(STRFTIME(\'%d\', "users"."created_at") AS INTEGER) > 10')
29
+ @table.project(@table[:created_at].year).group(@table[:created_at].year).to_sql.should
30
+ eq('SELECT CAST(STRFTIME(\'%Y\', "users"."created_at") AS INTEGER) FROM "users" GROUP BY CAST(STRFTIME(\'%Y\', "users"."created_at") AS INTEGER)')
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe "date_scopes AR specs" do
4
+ before(:each) do
5
+ User.destroy_all
6
+ @users = [
7
+ User.create(:created_at => Date.new(2009, 1, 29)),
8
+ User.create(:created_at => Date.new(2008, 1, 1))
9
+ ]
10
+ end
11
+
12
+ it "have working *_eq" do
13
+ @by_year = User.created_at_year_eq(2009)
14
+ @by_year.count.should == 1
15
+ @by_year.first.should == @users.first
16
+
17
+ @by_month = User.created_at_month_eq(1)
18
+ @by_month.count.should == 2
19
+ @by_month.should include(@users.first)
20
+ @by_month.should include(@users.second)
21
+
22
+ @by_month = User.created_at_day_eq(29)
23
+ @by_month.count.should == 1
24
+ @by_month.first.should == @users.first
25
+ end
26
+
27
+ it "have working *_years/*_months scopes" do
28
+ @years = User.order('created_at DESC').created_at_years.all
29
+ @years.count.should == 2
30
+ @years.first['created_at_year'].should == 2009
31
+ @years.last['created_at_year'].should == 2008
32
+
33
+ @months = User.order('created_at DESC').created_at_years.created_at_months.all
34
+ @months.count.should == 2
35
+ @months.first['created_at_month'].should == 1
36
+ @months.last['created_at_month'].should == 1
37
+
38
+ @days = User.order('created_at DESC').created_at_days.all
39
+ @days.count.should == 2
40
+ @days.first['created_at_day'].should == 29
41
+ @days.last['created_at_day'].should == 1
42
+ end
43
+
44
+ it "have working ascend_*/descend_* scopes" do
45
+ User.ascend_by_created_at.all.should eq(@users.reverse)
46
+ User.descend_by_created_at.all.should eq(@users)
47
+ end
48
+
49
+ it "gets column value with all_column" do
50
+ User.ascend_by_created_at.created_at_years.all_column.should == [2008, 2009]
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH << "." unless $LOAD_PATH.include?(".")
2
+
3
+ require 'logger'
4
+ require "bundler"
5
+
6
+ begin
7
+ Bundler.setup
8
+ Bundler.require
9
+ rescue Bundler::GemNotFound
10
+ raise RuntimeError, "Bundler couldn't find some gems." +
11
+ "Did you run `bundle install`?"
12
+ end
13
+
14
+ require 'active_record'
15
+ require 'support/fake_record'
16
+
17
+ # Fake connection to supress ConnectionNotEstablished on AR tests
18
+ ActiveRecord::Base.establish_connection(
19
+ "adapter" => "sqlite3",
20
+ "database" => ":memory:"
21
+ )
22
+
23
+ ActiveRecord::Base.logger = Logger.new(nil)
24
+ ActiveRecord::Base.send(:include, ArelDateScopes::ActiveRecord)
25
+
26
+ ActiveRecord::Schema.define :version => 0 do
27
+ create_table "users", :force => true do |t|
28
+ t.datetime :created_at
29
+ end
30
+ end
31
+
32
+ class User < ActiveRecord::Base
33
+ set_table_name 'users'
34
+ date_scopes_for :created_at
35
+ end
36
+
37
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
@@ -0,0 +1,90 @@
1
+ # Mock to make AREL work without regular database.
2
+ module FakeRecord
3
+ class Column < Struct.new(:name, :type)
4
+ end
5
+
6
+ class Connection
7
+ attr_reader :tables
8
+
9
+ def initialize
10
+ @tables = %w{ users }
11
+ @columns = {
12
+ 'users' => [
13
+ Column.new('id', :integer),
14
+ Column.new('created_at', :date)
15
+ ]
16
+ }
17
+ @primary_keys = {
18
+ 'users' => 'id'
19
+ }
20
+ end
21
+
22
+ def primary_key name
23
+ @primary_keys[name.to_s]
24
+ end
25
+
26
+ def table_exists? name
27
+ @tables.include? name.to_s
28
+ end
29
+
30
+ def columns name, message = nil
31
+ @columns[name.to_s]
32
+ end
33
+
34
+ def quote_table_name name
35
+ "\"#{name.to_s}\""
36
+ end
37
+
38
+ def quote_column_name name
39
+ "\"#{name.to_s}\""
40
+ end
41
+
42
+ def quote thing, column = nil
43
+ if column && column.type == :integer
44
+ return 'NULL' if thing.nil?
45
+ return thing.to_i
46
+ end
47
+
48
+ case thing
49
+ when true
50
+ "'t'"
51
+ when false
52
+ "'f'"
53
+ when nil
54
+ 'NULL'
55
+ when Numeric
56
+ thing
57
+ else
58
+ "'#{thing}'"
59
+ end
60
+ end
61
+ end
62
+
63
+ class ConnectionPool
64
+ class Spec < Struct.new(:config)
65
+ end
66
+
67
+ attr_reader :spec, :connection
68
+
69
+ def initialize(adapter)
70
+ @spec = Spec.new(adapter)
71
+ @connection = Connection.new
72
+ end
73
+
74
+ def with_connection
75
+ yield connection
76
+ end
77
+ end
78
+
79
+ class Base
80
+ attr_accessor :connection_pool
81
+
82
+ def initialize(adapter)
83
+ @connection_pool = ConnectionPool.new(adapter)
84
+ end
85
+
86
+ def connection
87
+ connection_pool.connection
88
+ end
89
+ end
90
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arel_date_scopes
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Victor Sokolov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-30 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: arel
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 2
32
+ version: "2"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 7
44
+ segments:
45
+ - 3
46
+ - 0
47
+ - 0
48
+ version: 3.0.0
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: activesupport
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 7
60
+ segments:
61
+ - 3
62
+ - 0
63
+ - 0
64
+ version: 3.0.0
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: sqlite3-ruby
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: rspec
83
+ prerelease: false
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ type: :development
94
+ version_requirements: *id005
95
+ description: SQL date functions for AREL 2 + AR 3 scopes (such as created_at_year_eq)
96
+ email: gzigzigzeo@gmail.com
97
+ executables: []
98
+
99
+ extensions: []
100
+
101
+ extra_rdoc_files: []
102
+
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - Gemfile
107
+ - Gemfile.lock
108
+ - README.rdoc
109
+ - Rakefile
110
+ - arel_date_scopes.gemspec
111
+ - lib/arel_date_scopes.rb
112
+ - lib/arel_date_scopes/active_record.rb
113
+ - lib/arel_date_scopes/arel.rb
114
+ - lib/arel_date_scopes/railtie.rb
115
+ - lib/arel_date_scopes/version.rb
116
+ - spec/arel_date_scopes/date_scopes_arel_spec.rb
117
+ - spec/arel_date_scopes/date_scopes_spec.rb
118
+ - spec/spec_helper.rb
119
+ - spec/support/fake_record.rb
120
+ has_rdoc: true
121
+ homepage: http://github.com/gzigzigzeo/arel_date_scopes
122
+ licenses: []
123
+
124
+ post_install_message:
125
+ rdoc_options:
126
+ - --charset=UTF-8
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ hash: 3
144
+ segments:
145
+ - 0
146
+ version: "0"
147
+ requirements: []
148
+
149
+ rubyforge_project:
150
+ rubygems_version: 1.6.0
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: SQL date functions for AREL 2 + AR 3 date scopes (such as created_at_year_eq)
154
+ test_files:
155
+ - spec/arel_date_scopes/date_scopes_arel_spec.rb
156
+ - spec/arel_date_scopes/date_scopes_spec.rb
157
+ - spec/spec_helper.rb
158
+ - spec/support/fake_record.rb