activerecord-deprecated_finders 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/.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