activerecord-deprecated_finders 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ notifications:
5
+ email:
6
+ - j@jonathanleighton.com
7
+ before_install:
8
+ - gem install bundler
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_record_deprecated_finders.gemspec
4
+ gemspec
5
+
6
+ if ENV['RAILS']
7
+ gem 'rails', path: ENV['RAILS']
8
+ else
9
+ gem 'rails', git: 'git://github.com/rails/rails', branch: 'master'
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jon Leighton
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,15 @@
1
+ # Active Record Deprecated Finders
2
+
3
+ This gem will be used to extract and deprecate old-style finder option
4
+ hashes in Active Record:
5
+
6
+ ``` ruby
7
+ Post.find(:all, conditions: { published_on: 2.weeks.ago }, limit: 5)
8
+ ```
9
+
10
+ It will be a dependency of Rails 4.0 to provide the deprecated
11
+ functionality.
12
+
13
+ It will be removed as a dependency in Rails 4.1, but users can manually
14
+ include it in their Gemfile and it will continue to be maintained until
15
+ Rails 5.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs = ["test"]
7
+ t.pattern = "test/**/*_test.rb"
8
+ t.ruby_opts = ['-w']
9
+ end
10
+
11
+ task :default => :test
12
+
13
+ specname = "activerecord-deprecated_finders.gemspec"
14
+ deps = `git ls-files`.split("\n") - [specname]
15
+
16
+ task :gemspec => specname
17
+
18
+ file specname => deps do
19
+ files = `git ls-files`.split("\n")
20
+ test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+
23
+ require 'erb'
24
+
25
+ File.open specname, 'w:utf-8' do |f|
26
+ f.write ERB.new(File.read("#{specname}.erb")).result(binding)
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/active_record/deprecated_finders/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jon Leighton"]
6
+ gem.email = ["j@jonathanleighton.com"]
7
+ gem.description = %q{This gem contains deprecated finder APIs extracted from Active Record.}
8
+ gem.summary = %q{This gem contains deprecated finder APIs extracted from Active Record.}
9
+ gem.homepage = "https://github.com/rails/activerecord-deprecated_finders"
10
+
11
+ gem.files = [".gitignore",".travis.yml","Gemfile","LICENSE","README.md","Rakefile","activerecord-deprecated_finders.gemspec","lib/active_record/deprecated_finders.rb","lib/active_record/deprecated_finders/association_builder.rb","lib/active_record/deprecated_finders/base.rb","lib/active_record/deprecated_finders/collection_proxy.rb","lib/active_record/deprecated_finders/dynamic_matchers.rb","lib/active_record/deprecated_finders/relation.rb","lib/active_record/deprecated_finders/version.rb","test/associations_test.rb","test/calculate_test.rb","test/default_scope_test.rb","test/dynamic_methods_test.rb","test/find_in_batches_test.rb","test/finder_options_test.rb","test/finder_test.rb","test/helper.rb","test/scope_test.rb","test/scoped_test.rb","test/update_all_test.rb","test/with_scope_test.rb"]
12
+ gem.test_files = ["test/associations_test.rb","test/calculate_test.rb","test/default_scope_test.rb","test/dynamic_methods_test.rb","test/find_in_batches_test.rb","test/finder_options_test.rb","test/finder_test.rb","test/helper.rb","test/scope_test.rb","test/scoped_test.rb","test/update_all_test.rb","test/with_scope_test.rb"]
13
+ gem.executables = []
14
+ gem.name = "activerecord-deprecated_finders"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ActiveRecord::DeprecatedFinders::VERSION
17
+
18
+ gem.add_development_dependency 'minitest', '>= 3'
19
+ gem.add_development_dependency 'activerecord', '~> 4.0.0.beta'
20
+ gem.add_development_dependency 'sqlite3', '~> 1.3'
21
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_support/lazy_load_hooks'
2
+ require 'active_record/deprecated_finders/version'
3
+
4
+ ActiveSupport.on_load(:active_record) do
5
+ require 'active_record/deprecated_finders/base'
6
+ require 'active_record/deprecated_finders/relation'
7
+ require 'active_record/deprecated_finders/dynamic_matchers'
8
+ require 'active_record/deprecated_finders/collection_proxy'
9
+ require 'active_record/deprecated_finders/association_builder'
10
+ end
@@ -0,0 +1,78 @@
1
+ require 'active_record/associations/builder/association'
2
+ require 'active_support/core_ext/module/aliasing'
3
+ require 'active_support/deprecation'
4
+
5
+ module ActiveRecord::Associations::Builder
6
+ class DeprecatedOptionsProc
7
+ attr_reader :options
8
+
9
+ def initialize(options)
10
+ options[:includes] = options.delete(:include) if options[:include]
11
+ options[:where] = options.delete(:conditions) if options[:conditions]
12
+ options[:extending] = options.delete(:extend) if options[:extend]
13
+
14
+ @options = options
15
+ end
16
+
17
+ def to_proc
18
+ options = self.options
19
+ proc do |owner|
20
+ if options[:where].respond_to?(:to_proc)
21
+ context = owner || self
22
+ where(context.instance_eval(&options[:where]))
23
+ .merge!(options.except(:where))
24
+ else
25
+ merge(options)
26
+ end
27
+ end
28
+ end
29
+
30
+ def arity
31
+ 1
32
+ end
33
+ end
34
+
35
+ class Association
36
+ DEPRECATED_OPTIONS = [:readonly, :order, :limit, :group, :having,
37
+ :offset, :select, :uniq, :include, :conditions, :extend]
38
+
39
+ self.valid_options += [:select, :conditions, :include, :extend, :readonly]
40
+
41
+ def initialize_with_deprecated_options(model, name, scope, options)
42
+ if scope.is_a?(Hash)
43
+ options = scope
44
+ deprecated_options = options.slice(*DEPRECATED_OPTIONS)
45
+
46
+ if deprecated_options.empty?
47
+ scope = nil
48
+ else
49
+ ActiveSupport::Deprecation.warn(
50
+ "The following options in your #{model.name}.#{macro} :#{name} declaration are deprecated: " \
51
+ "#{deprecated_options.keys.map(&:inspect).join(',')}. Please use a scope block instead. " \
52
+ "For example, the following:\n" \
53
+ "\n" \
54
+ " has_many :spam_comments, conditions: { spam: true }, class_name: 'Comment'\n" \
55
+ "\n" \
56
+ "should be rewritten as the following:\n" \
57
+ "\n" \
58
+ " has_many :spam_comments, -> { where spam: true }, class_name: 'Comment'\n"
59
+ )
60
+ scope = DeprecatedOptionsProc.new(deprecated_options)
61
+ options = options.except(*DEPRECATED_OPTIONS)
62
+ end
63
+ end
64
+
65
+ initialize_without_deprecated_options(model, name, scope, options)
66
+ end
67
+
68
+ alias_method_chain :initialize, :deprecated_options
69
+ end
70
+
71
+ class CollectionAssociation
72
+ include Module.new {
73
+ def valid_options
74
+ super + [:order, :group, :having, :limit, :offset, :uniq]
75
+ end
76
+ }
77
+ end
78
+ end
@@ -0,0 +1,151 @@
1
+ require 'active_support/deprecation'
2
+
3
+ module ActiveRecord
4
+ module DeprecatedFinders
5
+ class ScopeWrapper
6
+ def self.wrap(klass, scope)
7
+ if scope.is_a?(Hash)
8
+ ActiveSupport::Deprecation.warn(
9
+ "Calling #scope or #default_scope with a hash is deprecated. Please use a lambda " \
10
+ "containing a scope. E.g. scope :red, -> { where(color: 'red') }"
11
+ )
12
+
13
+ new(klass, scope)
14
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
15
+ new(klass, scope)
16
+ else
17
+ scope
18
+ end
19
+ end
20
+
21
+ def initialize(klass, scope)
22
+ @klass = klass
23
+ @scope = scope
24
+ end
25
+
26
+ def call(*args)
27
+ if @scope.respond_to?(:call)
28
+ result = @scope.call(*args)
29
+
30
+ if result.is_a?(Hash)
31
+ msg = "Returning a hash from a #scope or #default_scope block is deprecated. Please " \
32
+ "return an actual scope object instead. E.g. scope :red, -> { where(color: 'red') } " \
33
+ "rather than scope :red, -> { { conditions: { color: 'red' } } }. "
34
+
35
+ if @scope.respond_to?(:source_location)
36
+ msg << "(The scope was defined at #{@scope.source_location.join(':')}.)"
37
+ end
38
+
39
+ ActiveSupport::Deprecation.warn(msg)
40
+ end
41
+ else
42
+ result = @scope
43
+ end
44
+
45
+ if result.is_a?(Hash)
46
+ @klass.unscoped.apply_finder_options(result, true)
47
+ else
48
+ result
49
+ end
50
+ end
51
+ end
52
+
53
+ def default_scope(scope = {}, &block)
54
+ if block_given?
55
+ super ScopeWrapper.new(self, block), &nil
56
+ else
57
+ super ScopeWrapper.wrap(self, scope)
58
+ end
59
+ end
60
+
61
+ def scoped(options = nil)
62
+ ActiveSupport::Deprecation.warn("Model.scoped is deprecated. Please use Model.all instead.")
63
+ options ? all.apply_finder_options(options, true) : all
64
+ end
65
+
66
+ def all(options = nil)
67
+ options ? super().all(options) : super()
68
+ end
69
+
70
+ def scope(name, body = {}, &block)
71
+ super(name, ScopeWrapper.wrap(self, body), &block)
72
+ end
73
+
74
+ def with_scope(scope = {}, action = :merge)
75
+ ActiveSupport::Deprecation.warn(
76
+ "ActiveRecord::Base#with_scope and #with_exclusive_scope are deprecated. " \
77
+ "Please use ActiveRecord::Relation#scoping instead. (You can use #merge " \
78
+ "to merge multiple scopes together.)"
79
+ )
80
+
81
+ # If another Active Record class has been passed in, get its current scope
82
+ scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
83
+
84
+ previous_scope = self.current_scope
85
+
86
+ if scope.is_a?(Hash)
87
+ # Dup first and second level of hash (method and params).
88
+ scope = scope.dup
89
+ scope.each do |method, params|
90
+ scope[method] = params.dup unless params == true
91
+ end
92
+
93
+ scope.assert_valid_keys([ :find, :create ])
94
+ relation = construct_finder_arel(scope[:find] || {})
95
+ relation.default_scoped = true unless action == :overwrite
96
+
97
+ if previous_scope && previous_scope.create_with_value && scope[:create]
98
+ scope_for_create = if action == :merge
99
+ previous_scope.create_with_value.merge(scope[:create])
100
+ else
101
+ scope[:create]
102
+ end
103
+
104
+ relation = relation.create_with(scope_for_create)
105
+ else
106
+ scope_for_create = scope[:create]
107
+ scope_for_create ||= previous_scope.create_with_value if previous_scope
108
+ relation = relation.create_with(scope_for_create) if scope_for_create
109
+ end
110
+
111
+ scope = relation
112
+ end
113
+
114
+ scope = previous_scope.merge(scope) if previous_scope && action == :merge
115
+ scope.scoping { yield }
116
+ end
117
+
118
+ protected
119
+
120
+ # Works like with_scope, but discards any nested properties.
121
+ def with_exclusive_scope(method_scoping = {}, &block)
122
+ if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
123
+ raise ArgumentError, <<-MSG
124
+ New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
125
+
126
+ User.unscoped.where(:active => true)
127
+
128
+ Or call unscoped with a block:
129
+
130
+ User.unscoped do
131
+ User.where(:active => true).all
132
+ end
133
+
134
+ MSG
135
+ end
136
+ with_scope(method_scoping, :overwrite, &block)
137
+ end
138
+
139
+ private
140
+
141
+ def construct_finder_arel(options = {}, scope = nil)
142
+ relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options, true) : options
143
+ relation = scope.merge(relation) if scope
144
+ relation
145
+ end
146
+ end
147
+
148
+ class Base
149
+ extend DeprecatedFinders
150
+ end
151
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class CollectionProxy
4
+ module InterceptDynamicInstantiators
5
+ def method_missing(method, *args, &block)
6
+ match = DynamicMatchers::Method.match(klass, method)
7
+
8
+ if match && match.is_a?(DynamicMatchers::Instantiator)
9
+ scoping do
10
+ klass.send(method, *args) do |record|
11
+ proxy_association.add_to_target(record)
12
+ yield record if block_given?
13
+ end
14
+ end
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.inherited(subclass)
22
+ subclass.class_eval do
23
+ # Ensure this get included first
24
+ include ActiveRecord::Delegation::ClassSpecificRelation
25
+
26
+ # Now override the method_missing definition
27
+ include InterceptDynamicInstantiators
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,204 @@
1
+ require 'active_support/deprecation'
2
+
3
+ module ActiveRecord
4
+ module DynamicMatchers
5
+ module DeprecatedFinder
6
+ def body
7
+ <<-CODE
8
+ result = #{super}
9
+ result && block_given? ? yield(result) : result
10
+ CODE
11
+ end
12
+
13
+ def result
14
+ "all.apply_finder_options(options, true).#{super}"
15
+ end
16
+
17
+ def signature
18
+ "#{super}, options = {}"
19
+ end
20
+ end
21
+
22
+ module DeprecationWarning
23
+ def body
24
+ "#{deprecation_warning}\n#{super}"
25
+ end
26
+
27
+ def deprecation_warning
28
+ %{ActiveSupport::Deprecation.warn("This dynamic method is deprecated. Please use e.g. #{deprecation_alternative} instead.")}
29
+ end
30
+ end
31
+
32
+ module FindByDeprecationWarning
33
+ def body
34
+ <<-CODE
35
+ if block_given?
36
+ ActiveSupport::Deprecation.warn("Calling find_by or find_by! methods with a block is deprecated with no replacement.")
37
+ end
38
+
39
+ unless options.empty?
40
+ ActiveSupport::Deprecation.warn(
41
+ "Calling find_by or find_by! methods with options is deprecated. " \
42
+ "Build a scope instead, e.g. User.where('age > 21').find_by_name('Jon')."
43
+ )
44
+ end
45
+
46
+ #{super}
47
+ CODE
48
+ end
49
+ end
50
+
51
+ class FindBy
52
+ include DeprecatedFinder
53
+ include FindByDeprecationWarning
54
+ end
55
+
56
+ class FindByBang
57
+ include DeprecatedFinder
58
+ include FindByDeprecationWarning
59
+ end
60
+
61
+ class FindAllBy < Method
62
+ Method.matchers << self
63
+ include Finder
64
+ include DeprecatedFinder
65
+ include DeprecationWarning
66
+
67
+ def self.prefix
68
+ "find_all_by"
69
+ end
70
+
71
+ def finder
72
+ "where"
73
+ end
74
+
75
+ def result
76
+ "#{super}.to_a"
77
+ end
78
+
79
+ def deprecation_alternative
80
+ "Post.where(...).all"
81
+ end
82
+ end
83
+
84
+ class FindLastBy < Method
85
+ Method.matchers << self
86
+ include Finder
87
+ include DeprecatedFinder
88
+ include DeprecationWarning
89
+
90
+ def self.prefix
91
+ "find_last_by"
92
+ end
93
+
94
+ def finder
95
+ "where"
96
+ end
97
+
98
+ def result
99
+ "#{super}.last"
100
+ end
101
+
102
+ def deprecation_alternative
103
+ "Post.where(...).last"
104
+ end
105
+ end
106
+
107
+ class ScopedBy < Method
108
+ Method.matchers << self
109
+ include Finder
110
+ include DeprecationWarning
111
+
112
+ def self.prefix
113
+ "scoped_by"
114
+ end
115
+
116
+ def body
117
+ "#{deprecation_warning} \n where(#{attributes_hash})"
118
+ end
119
+
120
+ def deprecation_alternative
121
+ "Post.where(...)"
122
+ end
123
+ end
124
+
125
+ class Instantiator < Method
126
+ include DeprecationWarning
127
+
128
+ def self.dispatch(klass, attribute_names, instantiator, args, block)
129
+ if args.length == 1 && args.first.is_a?(Hash)
130
+ attributes = args.first.stringify_keys
131
+ conditions = attributes.slice(*attribute_names)
132
+ rest = [attributes.except(*attribute_names)]
133
+ else
134
+ raise ArgumentError, "too few arguments" unless args.length >= attribute_names.length
135
+
136
+ conditions = Hash[attribute_names.map.with_index { |n, i| [n, args[i]] }]
137
+ rest = args.drop(attribute_names.length)
138
+ end
139
+
140
+ klass.where(conditions).first ||
141
+ klass.create_with(conditions).send(instantiator, *rest, &block)
142
+ end
143
+
144
+ def signature
145
+ "*args, &block"
146
+ end
147
+
148
+ def body
149
+ <<-CODE
150
+ #{deprecation_warning}
151
+ #{self.class}.dispatch(self, #{attribute_names.inspect}, #{instantiator.inspect}, args, block)
152
+ CODE
153
+ end
154
+
155
+ def instantiator
156
+ raise NotImplementedError
157
+ end
158
+
159
+ def deprecation_alternative
160
+ "Post.#{self.class.prefix}#{self.class.suffix}(name: 'foo')"
161
+ end
162
+ end
163
+
164
+ class FindOrInitializeBy < Instantiator
165
+ Method.matchers << self
166
+
167
+ def self.prefix
168
+ "find_or_initialize_by"
169
+ end
170
+
171
+ def instantiator
172
+ "new"
173
+ end
174
+ end
175
+
176
+ class FindOrCreateBy < Instantiator
177
+ Method.matchers << self
178
+
179
+ def self.prefix
180
+ "find_or_create_by"
181
+ end
182
+
183
+ def instantiator
184
+ "create"
185
+ end
186
+ end
187
+
188
+ class FindOrCreateByBang < Instantiator
189
+ Method.matchers << self
190
+
191
+ def self.prefix
192
+ "find_or_create_by"
193
+ end
194
+
195
+ def self.suffix
196
+ "!"
197
+ end
198
+
199
+ def instantiator
200
+ "create!"
201
+ end
202
+ end
203
+ end
204
+ end