From 35397ef6652ef75ffdf8b7721ee39c52baef6b84 Mon Sep 17 00:00:00 2001 From: Spencer Markowski Date: Thu, 21 Mar 2024 13:45:55 -0400 Subject: [PATCH 1/3] Reorganize modules --- lib/stretchy/common.rb | 38 ----- lib/stretchy/model/common.rb | 40 ++++++ lib/stretchy/model/persistence.rb | 45 ++++++ lib/stretchy/model/refreshable.rb | 17 +++ lib/stretchy/null_relation.rb | 53 ------- lib/stretchy/persistence.rb | 43 ------ lib/stretchy/querying.rb | 6 +- lib/stretchy/record.rb | 8 +- lib/stretchy/refreshable.rb | 15 -- lib/stretchy/relations/finder_methods.rb | 20 +-- lib/stretchy/relations/null_relation.rb | 55 +++++++ lib/stretchy/relations/query_methods/none.rb | 4 +- lib/stretchy/relations/scoping.rb | 30 ++++ lib/stretchy/relations/scoping/default.rb | 136 ++++++++++++++++++ lib/stretchy/relations/scoping/named.rb | 70 +++++++++ .../relations/scoping/scope_registry.rb | 36 +++++ .../relations/search_option_methods.rb | 2 + lib/stretchy/scoping.rb | 28 ---- lib/stretchy/scoping/default.rb | 134 ----------------- lib/stretchy/scoping/named.rb | 68 --------- lib/stretchy/scoping/scope_registry.rb | 34 ----- spec/stretchy/null_relation_spec.rb | 4 +- 22 files changed, 444 insertions(+), 442 deletions(-) delete mode 100644 lib/stretchy/common.rb create mode 100644 lib/stretchy/model/common.rb create mode 100644 lib/stretchy/model/persistence.rb create mode 100644 lib/stretchy/model/refreshable.rb delete mode 100644 lib/stretchy/null_relation.rb delete mode 100644 lib/stretchy/persistence.rb delete mode 100644 lib/stretchy/refreshable.rb create mode 100644 lib/stretchy/relations/null_relation.rb create mode 100644 lib/stretchy/relations/scoping.rb create mode 100644 lib/stretchy/relations/scoping/default.rb create mode 100644 lib/stretchy/relations/scoping/named.rb create mode 100644 lib/stretchy/relations/scoping/scope_registry.rb delete mode 100644 lib/stretchy/scoping.rb delete mode 100644 lib/stretchy/scoping/default.rb delete mode 100644 lib/stretchy/scoping/named.rb delete mode 100644 lib/stretchy/scoping/scope_registry.rb diff --git a/lib/stretchy/common.rb b/lib/stretchy/common.rb deleted file mode 100644 index 0ccd57d..0000000 --- a/lib/stretchy/common.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Stretchy - module Common - extend ActiveSupport::Concern - - def highlights_for(attribute) - highlights[attribute.to_s] - end - - class_methods do - - # Set the default sort key to be used in sort operations - # - def default_sort_key(field = nil) - @default_sort_key = field unless field.nil? - @default_sort_key - end - - def default_size(size = nil) - @default_size = size unless size.nil? - @default_size - end - - def default_pipeline(pipeline = nil) - @default_pipeline = pipeline.to_s unless pipeline.nil? - @default_pipeline - end - - private - - # Return a Relation instance to chain queries - # - def relation - Relation.create(self, {}) - end - - end - end -end \ No newline at end of file diff --git a/lib/stretchy/model/common.rb b/lib/stretchy/model/common.rb new file mode 100644 index 0000000..b1211ef --- /dev/null +++ b/lib/stretchy/model/common.rb @@ -0,0 +1,40 @@ +module Stretchy + module Model + module Common + extend ActiveSupport::Concern + + def highlights_for(attribute) + highlights[attribute.to_s] + end + + class_methods do + + # Set the default sort key to be used in sort operations + # + def default_sort_key(field = nil) + @default_sort_key = field unless field.nil? + @default_sort_key + end + + def default_size(size = nil) + @default_size = size unless size.nil? + @default_size + end + + def default_pipeline(pipeline = nil) + @default_pipeline = pipeline.to_s unless pipeline.nil? + @default_pipeline + end + + private + + # Return a Relation instance to chain queries + # + def relation + Relation.create(self, {}) + end + + end + end + end +end diff --git a/lib/stretchy/model/persistence.rb b/lib/stretchy/model/persistence.rb new file mode 100644 index 0000000..f1158b9 --- /dev/null +++ b/lib/stretchy/model/persistence.rb @@ -0,0 +1,45 @@ +module Stretchy + module Model + module Persistence + extend ActiveSupport::Concern + + class_methods do + def create(*args) + self.new(*args).save + end + end + + def save + run_callbacks :save do + if new_record? + run_callbacks :create do + response = self.class.gateway.save(self.attributes) + self.id = response['_id'] + end + else + self.class.gateway.save(self.attributes) + end + self + end + end + + def destroy + run_callbacks :destroy do + delete + end + end + + def delete + self.class.gateway.delete(self.id)["result"] == 'deleted' + end + + def update(*args) + run_callbacks :update do + self.assign_attributes(*args) + self.save + end + end + + end + end +end diff --git a/lib/stretchy/model/refreshable.rb b/lib/stretchy/model/refreshable.rb new file mode 100644 index 0000000..ece1c7d --- /dev/null +++ b/lib/stretchy/model/refreshable.rb @@ -0,0 +1,17 @@ +module Stretchy + module Model + module Refreshable + extend ActiveSupport::Concern + + included do + after_save :refresh_index + after_destroy :refresh_index + end + + def refresh_index + self.class.refresh_index! + end + + end + end +end diff --git a/lib/stretchy/null_relation.rb b/lib/stretchy/null_relation.rb deleted file mode 100644 index c37af0d..0000000 --- a/lib/stretchy/null_relation.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module Stretchy - module NullRelation # :nodoc: - def pluck(*column_names) - [] - end - - def delete_all - 0 - end - - def update_all(_updates) - 0 - end - - def delete(_id_or_array) - 0 - end - - def empty? - true - end - - def none? - true - end - - def any? - false - end - - def one? - false - end - - def many? - false - end - - def exists?(_conditions = :none) - false - end - - def or(other) - other.spawn - end - - def exec_queries - @records = OpenStruct.new(klass: NullRelation, total: 0, results: []).freeze - end - end -end diff --git a/lib/stretchy/persistence.rb b/lib/stretchy/persistence.rb deleted file mode 100644 index 545c6d5..0000000 --- a/lib/stretchy/persistence.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Stretchy - module Persistence - extend ActiveSupport::Concern - - class_methods do - def create(*args) - self.new(*args).save - end - end - - def save - run_callbacks :save do - if new_record? - run_callbacks :create do - response = self.class.gateway.save(self.attributes) - self.id = response['_id'] - end - else - self.class.gateway.save(self.attributes) - end - self - end - end - - def destroy - run_callbacks :destroy do - delete - end - end - - def delete - self.class.gateway.delete(self.id)["result"] == 'deleted' - end - - def update(*args) - run_callbacks :update do - self.assign_attributes(*args) - self.save - end - end - - end -end \ No newline at end of file diff --git a/lib/stretchy/querying.rb b/lib/stretchy/querying.rb index 0bd7bb4..878a006 100644 --- a/lib/stretchy/querying.rb +++ b/lib/stretchy/querying.rb @@ -1,10 +1,10 @@ module Stretchy module Querying - delegate :first, :first!, :last, :last!, to: :all delegate :exists?, :any?, :many?, :includes, to: :all - delegate :rewhere, :eager_load, :create_with, :none, :unscope, to: :all - delegate :routing, :search_options, to: :all + delegate :rewhere, :eager_load, :create_with, :unscoped, to: :all + delegate *Stretchy::Relations::FinderMethods::METHODS, to: :all + delegate *Stretchy::Relations::SearchOptionMethods::METHODS, to: :all delegate *Stretchy::Relations::QueryMethods.registry, to: :all delegate *Stretchy::Relations::AggregationMethods.registry, to: :all diff --git a/lib/stretchy/record.rb b/lib/stretchy/record.rb index a4209be..561c88b 100644 --- a/lib/stretchy/record.rb +++ b/lib/stretchy/record.rb @@ -16,12 +16,12 @@ def self.inherited(base) include ActiveModel::Serializers::JSON include Stretchy::Model::Callbacks + include Stretchy::Model::Common + include Stretchy::Model::Persistence + include Stretchy::Model::Refreshable include Stretchy::Indexing::Bulk - include Stretchy::Persistence include Stretchy::Associations - include Stretchy::Refreshable - include Stretchy::Common - include Stretchy::Scoping + include Stretchy::Relations::Scoping include Stretchy::Utils include Stretchy::SharedScopes include Stretchy::Attributes diff --git a/lib/stretchy/refreshable.rb b/lib/stretchy/refreshable.rb deleted file mode 100644 index 4d40c92..0000000 --- a/lib/stretchy/refreshable.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Stretchy - module Refreshable - extend ActiveSupport::Concern - - included do - after_save :refresh_index - after_destroy :refresh_index - end - - def refresh_index - self.class.refresh_index! - end - - end -end diff --git a/lib/stretchy/relations/finder_methods.rb b/lib/stretchy/relations/finder_methods.rb index 069678b..435951e 100644 --- a/lib/stretchy/relations/finder_methods.rb +++ b/lib/stretchy/relations/finder_methods.rb @@ -3,6 +3,8 @@ module Relations module FinderMethods + METHODS = [:first, :first!, :last, :last!] + def first return results.first if @loaded spawn.first!.results.first @@ -39,24 +41,6 @@ def last! self end - # size is not permitted to the count API but queries are. - # - # if a query is supplied with `.count` then the user wants a count of the results - # matching the query - # - # however, the default_size is used to limit the number of results returned - # so we remove the size from the query and then call `.count` API - # - # but if the user supplies a limit, then we should assume they want a count of the results - # which could lead to some confusion - # suppose the user calls `.size(100).count` and the default_size is 100 - # if we remove size from the query, then the count could be greater than 100 - # - # I think the best way to handle this is to remove the size from the query only if it was - # applied by the default_size - # If the user supplies a limit, then we should assume they want a count of the results - # - # if size is called with a limit, def count return results.count if @loaded || @values[:size].present? spawn.count! diff --git a/lib/stretchy/relations/null_relation.rb b/lib/stretchy/relations/null_relation.rb new file mode 100644 index 0000000..6c9014a --- /dev/null +++ b/lib/stretchy/relations/null_relation.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Stretchy + module Relations + module NullRelation # :nodoc: + def pluck(*column_names) + [] + end + + def delete_all + 0 + end + + def update_all(_updates) + 0 + end + + def delete(_id_or_array) + 0 + end + + def empty? + true + end + + def none? + true + end + + def any? + false + end + + def one? + false + end + + def many? + false + end + + def exists?(_conditions = :none) + false + end + + def or(other) + other.spawn + end + + def exec_queries + @records = OpenStruct.new(klass: NullRelation, total: 0, results: []).freeze + end + end + end +end diff --git a/lib/stretchy/relations/query_methods/none.rb b/lib/stretchy/relations/query_methods/none.rb index 986a0c1..569d94f 100644 --- a/lib/stretchy/relations/query_methods/none.rb +++ b/lib/stretchy/relations/query_methods/none.rb @@ -6,11 +6,11 @@ module None # Returns a chainable relation with zero records. def none - extending(NullRelation) + extending(Stretchy::Relations::NullRelation) end def none! # :nodoc: - extending!(NullRelation) + extending!(Stretchy::Relations::NullRelation) end QueryMethods.register!(:none) diff --git a/lib/stretchy/relations/scoping.rb b/lib/stretchy/relations/scoping.rb new file mode 100644 index 0000000..7f97d18 --- /dev/null +++ b/lib/stretchy/relations/scoping.rb @@ -0,0 +1,30 @@ +module Stretchy + module Relations + module Scoping + extend ActiveSupport::Concern + + included do + include Default + include Named + end + + module ClassMethods + def base_class + self + end + + def current_scope + ScopeRegistry.new.registry[:current_scope][base_class.to_s] + # ScopeRegistry.value_for(:current_scope, base_class.to_s) + end + + def current_scope=(scope) #:nodoc: + ScopeRegistry.new.registry[:current_scope][base_class.to_s] = scope + # ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope) + end + end + + + end + end +end diff --git a/lib/stretchy/relations/scoping/default.rb b/lib/stretchy/relations/scoping/default.rb new file mode 100644 index 0000000..192d2d6 --- /dev/null +++ b/lib/stretchy/relations/scoping/default.rb @@ -0,0 +1,136 @@ +module Stretchy + module Relations + module Scoping + module Default + extend ActiveSupport::Concern + + included do + # Stores the default scope for the class. + class_attribute :default_scopes, instance_writer: false, instance_predicate: false + + self.default_scopes = [] + end + + module ClassMethods + # Returns a scope for the model without the previously set scopes. + # + # class Post < ActiveRecord::Base + # def self.default_scope + # where published: true + # end + # end + # + # Post.all # Fires "SELECT * FROM posts WHERE published = true" + # Post.unscoped.all # Fires "SELECT * FROM posts" + # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts" + # + # This method also accepts a block. All queries inside the block will + # not use the previously set scopes. + # + # Post.unscoped { + # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" + # } + def unscoped + block_given? ? relation.scoping { yield } : relation + end + + def before_remove_const #:nodoc: + self.current_scope = nil + end + + protected + + # Use this macro in your model to set a default scope for all operations on + # the model. + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true + # + # The +default_scope+ is also applied while creating/building a record. + # It is not applied while updating a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # (You can also pass any object which responds to +call+ to the + # +default_scope+ macro, and it will be called when building the + # default scope.) + # + # If you use multiple +default_scope+ declarations in your model then + # they will be merged together: + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # default_scope { where(rating: 'G') } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the + # parent or module defines a +default_scope+ and the child or including + # class defines a second one. + # + # If you need to do more complex things with a default scope, you can + # alternatively define it as a class method: + # + # class Article < ActiveRecord::Base + # def self.default_scope + # # Should return a scope, you can call 'super' here etc. + # end + # end + def default_scope(scope = nil) + scope = Proc.new if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + raise ArgumentError, + "Support for calling #default_scope without a block is removed. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + end + + self.default_scopes += [scope] + end + + def build_default_scope(base_rel = relation) # :nodoc: + if !self.is_a?(method(:default_scope).owner) + # The user has defined their own default scope method, so call that + evaluate_default_scope { default_scope } + elsif default_scopes.any? + evaluate_default_scope do + default_scopes.inject(base_rel) do |default_scope, scope| + default_scope.merge(base_rel.scoping { scope.call }) + end + end + end + end + + def ignore_default_scope? # :nodoc: + ScopeRegistry.value_for(:ignore_default_scope, self) + end + + def ignore_default_scope=(ignore) # :nodoc: + ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore) + end + + # The ignore_default_scope flag is used to prevent an infinite recursion + # situation where a default scope references a scope which has a default + # scope which references a scope... + def evaluate_default_scope # :nodoc: + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + yield + ensure + self.ignore_default_scope = false + end + end + end + end + end + end +end diff --git a/lib/stretchy/relations/scoping/named.rb b/lib/stretchy/relations/scoping/named.rb new file mode 100644 index 0000000..b3f227f --- /dev/null +++ b/lib/stretchy/relations/scoping/named.rb @@ -0,0 +1,70 @@ +module Stretchy + module Relations + module Scoping + module Named + extend ActiveSupport::Concern + + module ClassMethods + def all(options={}) + if current_scope + current_scope.clone + else + default_scoped + end + end + + def default_scoped # :nodoc: + relation.merge(build_default_scope) + end + + # Collects attributes from scopes that should be applied when creating + # an AR instance for the particular class this is called on. + def scope_attributes # :nodoc: + all.scope_for_create + end + + # Are there default attributes associated with this scope? + def scope_attributes? # :nodoc: + current_scope || default_scopes.any? + end + + def scope(name, body, &block) + if dangerous_class_method?(name) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but there's already defined " \ + "a class method with the same name." + end + + extension = Module.new(&block) if block + + singleton_class.send(:define_method, name) do |*args| + scope = all.scoping { body.call(*args) } + scope = scope.extending(extension) if extension + + scope || all + end + end + + BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) + + private + def dangerous_class_method?(method_name) + BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Model, self.superclass) + end + + def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc + if klass.respond_to?(name, true) + if superklass.respond_to?(name, true) + klass.method(name).owner != superklass.method(name).owner + else + true + end + else + false + end + end + end + end + end + end +end diff --git a/lib/stretchy/relations/scoping/scope_registry.rb b/lib/stretchy/relations/scoping/scope_registry.rb new file mode 100644 index 0000000..40aed8f --- /dev/null +++ b/lib/stretchy/relations/scoping/scope_registry.rb @@ -0,0 +1,36 @@ +module Stretchy + module Relations + module Scoping + class ScopeRegistry # :nodoc: + # extend ActiveSupport::PerThreadRegistry + thread_mattr_accessor :registry + + VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope] + + # def initialize + self.registry = Hash.new { |hash, key| hash[key] = {} } + # end + + # Obtains the value for a given +scope_name+ and +variable_name+. + def self.value_for(scope_type, variable_name) + raise_invalid_scope_type!(scope_type) + self.registry[scope_type][variable_name] + end + + # Sets the +value+ for a given +scope_type+ and +variable_name+. + def self.set_value_for(scope_type, variable_name, value) + raise_invalid_scope_type!(scope_type) + self.registry[scope_type][variable_name] = value + end + + private + + def raise_invalid_scope_type!(scope_type) + if !VALID_SCOPE_TYPES.include?(scope_type) + raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + end + end + end + end + end +end diff --git a/lib/stretchy/relations/search_option_methods.rb b/lib/stretchy/relations/search_option_methods.rb index 10b6896..38d7d23 100644 --- a/lib/stretchy/relations/search_option_methods.rb +++ b/lib/stretchy/relations/search_option_methods.rb @@ -4,6 +4,8 @@ module Relations module SearchOptionMethods extend ActiveSupport::Concern + METHODS = [:routing, :search_options] + def routing(args) check_if_method_has_arguments!(:routing, args) spawn.routing!(args) diff --git a/lib/stretchy/scoping.rb b/lib/stretchy/scoping.rb deleted file mode 100644 index 4825ffb..0000000 --- a/lib/stretchy/scoping.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Stretchy - module Scoping - extend ActiveSupport::Concern - - included do - include Default - include Named - end - - module ClassMethods - def base_class - self - end - - def current_scope - ScopeRegistry.new.registry[:current_scope][base_class.to_s] - # ScopeRegistry.value_for(:current_scope, base_class.to_s) - end - - def current_scope=(scope) #:nodoc: - ScopeRegistry.new.registry[:current_scope][base_class.to_s] = scope - # ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope) - end - end - - - end -end diff --git a/lib/stretchy/scoping/default.rb b/lib/stretchy/scoping/default.rb deleted file mode 100644 index 9f7abab..0000000 --- a/lib/stretchy/scoping/default.rb +++ /dev/null @@ -1,134 +0,0 @@ -module Stretchy - module Scoping - module Default - extend ActiveSupport::Concern - - included do - # Stores the default scope for the class. - class_attribute :default_scopes, instance_writer: false, instance_predicate: false - - self.default_scopes = [] - end - - module ClassMethods - # Returns a scope for the model without the previously set scopes. - # - # class Post < ActiveRecord::Base - # def self.default_scope - # where published: true - # end - # end - # - # Post.all # Fires "SELECT * FROM posts WHERE published = true" - # Post.unscoped.all # Fires "SELECT * FROM posts" - # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts" - # - # This method also accepts a block. All queries inside the block will - # not use the previously set scopes. - # - # Post.unscoped { - # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" - # } - def unscoped - block_given? ? relation.scoping { yield } : relation - end - - def before_remove_const #:nodoc: - self.current_scope = nil - end - - protected - - # Use this macro in your model to set a default scope for all operations on - # the model. - # - # class Article < ActiveRecord::Base - # default_scope { where(published: true) } - # end - # - # Article.all # => SELECT * FROM articles WHERE published = true - # - # The +default_scope+ is also applied while creating/building a record. - # It is not applied while updating a record. - # - # Article.new.published # => true - # Article.create.published # => true - # - # (You can also pass any object which responds to +call+ to the - # +default_scope+ macro, and it will be called when building the - # default scope.) - # - # If you use multiple +default_scope+ declarations in your model then - # they will be merged together: - # - # class Article < ActiveRecord::Base - # default_scope { where(published: true) } - # default_scope { where(rating: 'G') } - # end - # - # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' - # - # This is also the case with inheritance and module includes where the - # parent or module defines a +default_scope+ and the child or including - # class defines a second one. - # - # If you need to do more complex things with a default scope, you can - # alternatively define it as a class method: - # - # class Article < ActiveRecord::Base - # def self.default_scope - # # Should return a scope, you can call 'super' here etc. - # end - # end - def default_scope(scope = nil) - scope = Proc.new if block_given? - - if scope.is_a?(Relation) || !scope.respond_to?(:call) - raise ArgumentError, - "Support for calling #default_scope without a block is removed. For example instead " \ - "of `default_scope where(color: 'red')`, please use " \ - "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ - "self.default_scope.)" - end - - self.default_scopes += [scope] - end - - def build_default_scope(base_rel = relation) # :nodoc: - if !self.is_a?(method(:default_scope).owner) - # The user has defined their own default scope method, so call that - evaluate_default_scope { default_scope } - elsif default_scopes.any? - evaluate_default_scope do - default_scopes.inject(base_rel) do |default_scope, scope| - default_scope.merge(base_rel.scoping { scope.call }) - end - end - end - end - - def ignore_default_scope? # :nodoc: - ScopeRegistry.value_for(:ignore_default_scope, self) - end - - def ignore_default_scope=(ignore) # :nodoc: - ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore) - end - - # The ignore_default_scope flag is used to prevent an infinite recursion - # situation where a default scope references a scope which has a default - # scope which references a scope... - def evaluate_default_scope # :nodoc: - return if ignore_default_scope? - - begin - self.ignore_default_scope = true - yield - ensure - self.ignore_default_scope = false - end - end - end - end - end -end diff --git a/lib/stretchy/scoping/named.rb b/lib/stretchy/scoping/named.rb deleted file mode 100644 index 63961d4..0000000 --- a/lib/stretchy/scoping/named.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Stretchy - module Scoping - module Named - extend ActiveSupport::Concern - - module ClassMethods - def all(options={}) - if current_scope - current_scope.clone - else - default_scoped - end - end - - def default_scoped # :nodoc: - relation.merge(build_default_scope) - end - - # Collects attributes from scopes that should be applied when creating - # an AR instance for the particular class this is called on. - def scope_attributes # :nodoc: - all.scope_for_create - end - - # Are there default attributes associated with this scope? - def scope_attributes? # :nodoc: - current_scope || default_scopes.any? - end - - def scope(name, body, &block) - if dangerous_class_method?(name) - raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ - "on the model \"#{self.name}\", but there's already defined " \ - "a class method with the same name." - end - - extension = Module.new(&block) if block - - singleton_class.send(:define_method, name) do |*args| - scope = all.scoping { body.call(*args) } - scope = scope.extending(extension) if extension - - scope || all - end - end - - BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) - - private - def dangerous_class_method?(method_name) - BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Model, self.superclass) - end - - def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc - if klass.respond_to?(name, true) - if superklass.respond_to?(name, true) - klass.method(name).owner != superklass.method(name).owner - else - true - end - else - false - end - end - end - end - end -end diff --git a/lib/stretchy/scoping/scope_registry.rb b/lib/stretchy/scoping/scope_registry.rb deleted file mode 100644 index b69d44f..0000000 --- a/lib/stretchy/scoping/scope_registry.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Stretchy - module Scoping - class ScopeRegistry # :nodoc: - # extend ActiveSupport::PerThreadRegistry - thread_mattr_accessor :registry - - VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope] - - # def initialize - self.registry = Hash.new { |hash, key| hash[key] = {} } - # end - - # Obtains the value for a given +scope_name+ and +variable_name+. - def self.value_for(scope_type, variable_name) - raise_invalid_scope_type!(scope_type) - self.registry[scope_type][variable_name] - end - - # Sets the +value+ for a given +scope_type+ and +variable_name+. - def self.set_value_for(scope_type, variable_name, value) - raise_invalid_scope_type!(scope_type) - self.registry[scope_type][variable_name] = value - end - - private - - def raise_invalid_scope_type!(scope_type) - if !VALID_SCOPE_TYPES.include?(scope_type) - raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" - end - end - end - end -end \ No newline at end of file diff --git a/spec/stretchy/null_relation_spec.rb b/spec/stretchy/null_relation_spec.rb index 5cad0a4..06b99e7 100644 --- a/spec/stretchy/null_relation_spec.rb +++ b/spec/stretchy/null_relation_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe Stretchy::NullRelation do - let (:subject) { Stretchy::Relation.extend Stretchy::NullRelation } +describe Stretchy::Relations::NullRelation do + let (:subject) { Stretchy::Relation.extend Stretchy::Relations::NullRelation } it 'returns an empty array for pluck' do expect(subject.pluck).to eq([]) From 262787ffb44e6e384f1c5168a641f5f620c8d04c Mon Sep 17 00:00:00 2001 From: Spencer Markowski Date: Thu, 21 Mar 2024 13:53:13 -0400 Subject: [PATCH 2/3] Fix behaves like spec --- spec/support/behaves_like_stretchy_model.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/support/behaves_like_stretchy_model.rb b/spec/support/behaves_like_stretchy_model.rb index f0a2f83..acf0e75 100644 --- a/spec/support/behaves_like_stretchy_model.rb +++ b/spec/support/behaves_like_stretchy_model.rb @@ -15,8 +15,8 @@ expect(model_class).to include(Stretchy::Associations) end - it 'includes Stretchy::Refreshable' do - expect(model_class).to include(Stretchy::Refreshable) + it 'includes Stretchy::Model::Refreshable' do + expect(model_class).to include(Stretchy::Model::Refreshable) end it 'counts' do From b75ea223c384d0a0164c791bd651095200501dfd Mon Sep 17 00:00:00 2001 From: Spencer Markowski Date: Thu, 21 Mar 2024 13:55:38 -0400 Subject: [PATCH 3/3] Fix behaves like spec --- spec/support/behaves_like_stretchy_model.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/behaves_like_stretchy_model.rb b/spec/support/behaves_like_stretchy_model.rb index acf0e75..c2cf881 100644 --- a/spec/support/behaves_like_stretchy_model.rb +++ b/spec/support/behaves_like_stretchy_model.rb @@ -25,7 +25,7 @@ expect(model_class.count).to be_a(Numeric) end - context 'pipelins' do + context 'pipelines' do it 'responds to default_pipeline' do expect(model_class).to respond_to(:default_pipeline) end