dynamic_default_scoping 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.
@@ -0,0 +1,5 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ *.swp
5
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format documentation
3
+ --fail-fast
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dynamic_default_scoping.gemspec
4
+ gemspec
@@ -0,0 +1,53 @@
1
+ = Dynamic Default Scoping
2
+
3
+ == What is the library for?
4
+
5
+ Well, at the moment ActiveRecord (3.0.3) does not allow to pass
6
+ procs and lambdas to +default_scope+. That's strange, isn't it?
7
+ Because +scope+ successfully handles arguments of those types,
8
+ so why +default_scope+ does not?
9
+
10
+ == How to use?
11
+
12
+ Here is an example:
13
+
14
+ class Post < ActiveRecord::Base
15
+ include DynamicDefaultScoping
16
+
17
+ default_scope :language, lambda { |*args|
18
+ case locale = (args[0].presence || I18n.locale).to_s
19
+ when 'any', 'all'
20
+ where :locale => I18n.available_locales.map(&:to_s)
21
+ else
22
+ where :locale => locale
23
+ end
24
+ }
25
+ end
26
+
27
+ You can overwrite the default value for the locale attribute
28
+ calling the scope by name and passing another value.
29
+
30
+ == What are the constrains?
31
+
32
+ Because of the internal implementation of ActiveRecord and friends,
33
+ the library *should be included only after* all calls to
34
+ the regular +default_scope+ and +scope+, especially when they use
35
+ new aRel syntax like +where+, +order+, +limit+, etc.
36
+
37
+ Here is how it should be:
38
+
39
+ class Article < ActiveRecord::Base
40
+ default_scope order(:created_at).reverse_order
41
+
42
+ scope :published, where(:published => true)
43
+
44
+ include DynamicDefaultScoping
45
+
46
+ default_scope :recent, lambda {
47
+ where [ 'created_at > ?', Time.now - 5.days ]
48
+ }
49
+ end
50
+
51
+ == When there will be a normal implementation of this feature?
52
+
53
+ http://rails.lighthouseapp.com/projects/8994/tickets/1812-default_scope-cant-take-procs
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+ Bundler.setup
4
+
5
+ require 'rspec'
6
+ require 'rspec/core/rake_task'
7
+
8
+ Rspec::Core::RakeTask.new(:spec) do |spec|
9
+ spec.pattern = "spec/**/*_spec.rb"
10
+ end
11
+
12
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dynamic_default_scoping/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dynamic_default_scoping"
7
+ s.version = DynamicDefaultScoping::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ivan Ukhov"]
10
+ s.email = ["uvsoft@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Make ActiveRecord::Base.default_scope take a proc and have a name.}
13
+ s.description = %q{Being included in a model, the library patches ActiveRecord::Base.default_scope to take a proc and have an optional name.}
14
+
15
+ s.rubyforge_project = "dynamic_default_scoping"
16
+
17
+ s.add_development_dependency 'mysql2', '~> 0.2.6'
18
+ s.add_development_dependency 'rspec', '~> 2.4.0'
19
+
20
+ s.add_dependency 'rails', '3.0.3'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,69 @@
1
+ module DynamicDefaultScoping
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ @relation = nil
6
+ end
7
+
8
+ module ClassMethods
9
+ def default_scope name, options = nil
10
+ if options.present?
11
+ scope name, options
12
+ else
13
+ name, options = nil, name
14
+ end
15
+
16
+ reset_scoped_methods
17
+
18
+ scoping = self.default_scoping.dup
19
+ last = scoping.pop
20
+
21
+ scope = if last.respond_to?(:call) || options.respond_to?(:call)
22
+ lambda do
23
+ construct_finder_arel \
24
+ options.respond_to?(:call) ? options.call : options,
25
+ last.respond_to?(:call) ? last.call : last
26
+ end
27
+ else
28
+ construct_finder_arel options, last
29
+ end
30
+
31
+ self.default_scoping = scoping << scope
32
+ end
33
+
34
+ def scoped options = nil
35
+ if options
36
+ scoped.apply_finder_options options
37
+ else
38
+ last = current_scoped_methods
39
+ last ? relation.merge(last) : relation.clone
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def current_scoped_methods
46
+ method = scoped_methods.last
47
+ method.kind_of?(Proc) ? unscoped(&method) : method
48
+ end
49
+
50
+ private
51
+
52
+ def relation
53
+ @relation ||= DynamicDefaultScoping::Relation.new self, arel_table
54
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
55
+ end
56
+ end
57
+
58
+ class Relation < ::ActiveRecord::Relation
59
+ protected
60
+
61
+ def method_missing method, *args, &block
62
+ if @klass.scopes[method].present?
63
+ merge @klass.unscoped { @klass.send method, *args, &block }
64
+ else
65
+ super
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module DynamicDefaultScoping
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,43 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require 'active_support/all'
5
+ require 'active_record'
6
+ require 'dynamic_default_scoping'
7
+
8
+ RSpec.configure do |config|
9
+ config.before(:each) do
10
+ Post.unscoped.delete_all
11
+ end
12
+ end
13
+
14
+ ActiveRecord::Base.establish_connection \
15
+ :adapter => 'mysql2',
16
+ :hostname => 'localhost',
17
+ :database => 'dynamic_default_scoping',
18
+ :username => 'root',
19
+ :password => ''
20
+
21
+ connection = ActiveRecord::Base.connection
22
+ connection.drop_table 'posts' if connection.table_exists? 'posts'
23
+ connection.create_table 'posts' do |table|
24
+ table.string :locale
25
+ table.timestamps
26
+ end
27
+
28
+ LOCALES = [ :en, :ru ]
29
+
30
+ class Post < ActiveRecord::Base
31
+ scope :recent, limit(5)
32
+
33
+ include DynamicDefaultScoping
34
+
35
+ default_scope :language, lambda { |*args|
36
+ case locale = (args[0].presence || I18n.locale).to_s
37
+ when 'any', 'all'
38
+ where :locale => LOCALES.map(&:to_s)
39
+ else
40
+ where :locale => locale
41
+ end
42
+ }
43
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe DynamicDefaultScoping do
4
+ def should_have_valid_locale *args
5
+ options = args.extract_options!
6
+ locale = (options[:locale].presence || I18n.locale).to_s
7
+ args.flatten.all?{ |post| post.locale == locale }.should be_true
8
+ end
9
+
10
+ let(:locales) { LOCALES }
11
+ let(:per_locale) do
12
+ hash = {}
13
+ locales.each_with_index { |locale, i| hash[locale] = 3 + i }
14
+ hash
15
+ end
16
+
17
+ describe 'when switching between locales' do
18
+ before do
19
+ locales.each do |locale|
20
+ per_locale[locale].times do
21
+ Post.create! :locale => locale.to_s
22
+ end
23
+ end
24
+ end
25
+
26
+ around do |block|
27
+ locales.each do |locale|
28
+ I18n.locale = locale
29
+ block.call
30
+ end
31
+ end
32
+
33
+ context 'when the locale is not specified' do
34
+ it 'finds records only in the current locale' do
35
+ should_have_valid_locale Post.first, Post.all
36
+ end
37
+
38
+ it 'counts records only in the current locale' do
39
+ Post.count.should == per_locale[I18n.locale]
40
+ end
41
+ end
42
+
43
+ context 'when the locale is specified' do
44
+ shared_examples_for 'well localized class' do
45
+ it 'generates correct query' do
46
+ sql = @scoped.to_sql
47
+ sql.should include %[`locale` = '#{ @locale }']
48
+ end
49
+
50
+ it 'finds records in the specified locale' do
51
+ all = @scoped.all
52
+ all.length.should == per_locale[@locale]
53
+ should_have_valid_locale all, :locale => @locale
54
+ end
55
+
56
+ it 'counts records in the specified locale' do
57
+ @scoped.count.should == per_locale[@locale]
58
+ end
59
+ end
60
+
61
+ context 'when there is another scope before' do
62
+ around do |block|
63
+ locales.each do |locale|
64
+ @locale = locale
65
+ @scoped = Post.recent.language(locale)
66
+ block.call
67
+ end
68
+ end
69
+
70
+ it_behaves_like 'well localized class'
71
+ end
72
+
73
+ context 'when there is another scope after' do
74
+ around do |block|
75
+ locales.each do |locale|
76
+ @locale = locale
77
+ @scoped = Post.language(locale).recent
78
+ block.call
79
+ end
80
+ end
81
+
82
+ it_behaves_like 'well localized class'
83
+ end
84
+
85
+ context 'when there is no another scope' do
86
+ around do |block|
87
+ locales.each do |locale|
88
+ @locale = locale
89
+ @scoped = Post.language(locale)
90
+ block.call
91
+ end
92
+ end
93
+
94
+ it_behaves_like 'well localized class'
95
+ end
96
+ end
97
+
98
+ context 'when we are interested in all locales' do
99
+ let(:scoped) { Post.language :any }
100
+
101
+ it 'finds all records' do
102
+ sql = scoped.to_sql
103
+ locales.each do |locale|
104
+ sql.should =~ %r[`locale` IN \(.*'#{ locale }'.*\)]
105
+ end
106
+ scoped.length.should == per_locale.values.sum
107
+ end
108
+
109
+ it 'counts all records' do
110
+ scoped.count.should == per_locale.values.sum
111
+ end
112
+ end
113
+ end
114
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamic_default_scoping
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ivan Ukhov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-30 00:00:00 +03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 27
29
+ segments:
30
+ - 0
31
+ - 2
32
+ - 6
33
+ version: 0.2.6
34
+ type: :development
35
+ requirement: *id001
36
+ name: mysql2
37
+ - !ruby/object:Gem::Dependency
38
+ prerelease: false
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 31
45
+ segments:
46
+ - 2
47
+ - 4
48
+ - 0
49
+ version: 2.4.0
50
+ type: :development
51
+ requirement: *id002
52
+ name: rspec
53
+ - !ruby/object:Gem::Dependency
54
+ prerelease: false
55
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - "="
59
+ - !ruby/object:Gem::Version
60
+ hash: 1
61
+ segments:
62
+ - 3
63
+ - 0
64
+ - 3
65
+ version: 3.0.3
66
+ type: :runtime
67
+ requirement: *id003
68
+ name: rails
69
+ description: Being included in a model, the library patches ActiveRecord::Base.default_scope to take a proc and have an optional name.
70
+ email:
71
+ - uvsoft@gmail.com
72
+ executables: []
73
+
74
+ extensions: []
75
+
76
+ extra_rdoc_files: []
77
+
78
+ files:
79
+ - .gitignore
80
+ - .rspec
81
+ - Gemfile
82
+ - README.rdoc
83
+ - Rakefile
84
+ - dynamic_default_scoping.gemspec
85
+ - lib/dynamic_default_scoping.rb
86
+ - lib/dynamic_default_scoping/version.rb
87
+ - spec/spec_helper.rb
88
+ - spec/unit/dynamic_default_scoping_spec.rb
89
+ has_rdoc: true
90
+ homepage: ""
91
+ licenses: []
92
+
93
+ post_install_message:
94
+ rdoc_options: []
95
+
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ requirements: []
117
+
118
+ rubyforge_project: dynamic_default_scoping
119
+ rubygems_version: 1.4.2
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Make ActiveRecord::Base.default_scope take a proc and have a name.
123
+ test_files:
124
+ - spec/spec_helper.rb
125
+ - spec/unit/dynamic_default_scoping_spec.rb