From 4391a24677548a091712f2645e1fa8176879f4de Mon Sep 17 00:00:00 2001 From: Daniel Turner Date: Thu, 3 Jul 2014 12:46:40 -0500 Subject: [PATCH 1/5] Modify association_proxy's method_missing to allow for scopes. --- lib/her/model/associations/association.rb | 1 + lib/her/model/associations/association_proxy.rb | 4 ++++ spec/model/associations_spec.rb | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/lib/her/model/associations/association.rb b/lib/her/model/associations/association.rb index 6fc96a87..dc809a31 100644 --- a/lib/her/model/associations/association.rb +++ b/lib/her/model/associations/association.rb @@ -4,6 +4,7 @@ module Associations class Association # @private attr_accessor :params + attr_reader :klass # @private def initialize(parent, opts = {}) diff --git a/lib/her/model/associations/association_proxy.rb b/lib/her/model/associations/association_proxy.rb index f770a9e5..d9adc479 100644 --- a/lib/her/model/associations/association_proxy.rb +++ b/lib/her/model/associations/association_proxy.rb @@ -32,6 +32,10 @@ def method_missing(name, *args, &block) return association.fetch.object_id end + # Does the relation respond to this method (e.g. is it a scope) + if association.klass.respond_to?(name) && association.klass.singleton_methods(false).include?(name) + return association.klass.send(name, *args, &block) + end # create a proxy to the fetched object's method metaclass = (class << self; self; end) metaclass.install_proxy_methods 'association.fetch', name diff --git a/spec/model/associations_spec.rb b/spec/model/associations_spec.rb index 8d293403..c6f57d51 100644 --- a/spec/model/associations_spec.rb +++ b/spec/model/associations_spec.rb @@ -107,6 +107,11 @@ [200, {}, { :organization => { :id => 2, :name => "Bluth Company" } }.to_json] end end + stub.get("/comments") do |env| + if env[:params]["locked"] == "true" + [200, {}, [{ :comment => { :id => 5, :body => "Is this the tiny town from Footloose?", :locked => true } }].to_json] + end + end end end @@ -119,6 +124,7 @@ spawn_model "Foo::Comment" do belongs_to :user parse_root_in_json true + scope :locked_scope, lambda { where(locked: true) } end spawn_model "Foo::Post" do belongs_to :admin, :class_name => 'Foo::User' @@ -228,6 +234,11 @@ params[:comments].length.should eq(2) end + it 'supports scopes on assoications' do + locked_comments = @user_with_included_data.comments.locked_scope + locked_comments.first.id.should eq(5) + end + [:create, :save_existing, :destroy].each do |type| context "after #{type}" do let(:subject) { self.send("user_with_included_data_after_#{type}")} From 0def4c89302e73ad8ee3135dc07f25f5662dc3c8 Mon Sep 17 00:00:00 2001 From: Daniel Turner Date: Thu, 3 Jul 2014 12:49:22 -0500 Subject: [PATCH 2/5] update comment --- lib/her/model/associations/association_proxy.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/her/model/associations/association_proxy.rb b/lib/her/model/associations/association_proxy.rb index d9adc479..bc2d71e6 100644 --- a/lib/her/model/associations/association_proxy.rb +++ b/lib/her/model/associations/association_proxy.rb @@ -32,7 +32,8 @@ def method_missing(name, *args, &block) return association.fetch.object_id end - # Does the relation respond to this method (e.g. is it a scope) + # Does the underlying class of this association support this method + # at the class level, if so its likely to be a scope. if association.klass.respond_to?(name) && association.klass.singleton_methods(false).include?(name) return association.klass.send(name, *args, &block) end From eb55314471ac8be2a604102e06030b5deb30b15e Mon Sep 17 00:00:00 2001 From: Daniel Turner Date: Thu, 3 Jul 2014 16:53:04 -0500 Subject: [PATCH 3/5] Need to extract parents primary key if its in the collection path --- lib/her/model/associations/association.rb | 9 +++++++++ lib/her/model/associations/association_proxy.rb | 2 +- spec/model/associations_spec.rb | 14 ++++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/her/model/associations/association.rb b/lib/her/model/associations/association.rb index dc809a31..9de23b23 100644 --- a/lib/her/model/associations/association.rb +++ b/lib/her/model/associations/association.rb @@ -16,6 +16,15 @@ def initialize(parent, opts = {}) @name = @opts[:name] end + def call_scope(name, *args, &block) + parent_id_string = "#{@parent.class.to_s.demodulize.downcase}_#{@parent.class.primary_key}" + parent_id = @parent.send(@parent.class.primary_key) + if klass.collection_path[parent_id_string] + with_parent_id = klass.send(:where, parent_id_string => parent_id) + end + with_parent_id.send(name, *args, &block) + end + # @private def self.proxy(parent, opts = {}) AssociationProxy.new new(parent, opts) diff --git a/lib/her/model/associations/association_proxy.rb b/lib/her/model/associations/association_proxy.rb index bc2d71e6..ffd6c47e 100644 --- a/lib/her/model/associations/association_proxy.rb +++ b/lib/her/model/associations/association_proxy.rb @@ -35,7 +35,7 @@ def method_missing(name, *args, &block) # Does the underlying class of this association support this method # at the class level, if so its likely to be a scope. if association.klass.respond_to?(name) && association.klass.singleton_methods(false).include?(name) - return association.klass.send(name, *args, &block) + return association.call_scope(name, *args, &block) end # create a proxy to the fetched object's method metaclass = (class << self; self; end) diff --git a/spec/model/associations_spec.rb b/spec/model/associations_spec.rb index c6f57d51..e8607f0b 100644 --- a/spec/model/associations_spec.rb +++ b/spec/model/associations_spec.rb @@ -89,7 +89,13 @@ builder.adapter :test do |stub| stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke", :comments => [{ :comment => { :id => 2, :body => "Tobias, you blow hard!", :user_id => 1 } }, { :comment => { :id => 3, :body => "I wouldn't mind kissing that man between the cheeks, so to speak", :user_id => 1 } }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 1, :name => "Bluth Company" }, :organization_id => 1 }.to_json] } stub.get("/users/2") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :organization_id => 2 }.to_json] } - stub.get("/users/1/comments") { |env| [200, {}, [{ :comment => { :id => 4, :body => "They're having a FIRESALE?" } }].to_json] } + stub.get("/users/1/comments") do |env| + if env[:params]["locked"] == "true" + [200, {}, [{ :comment => { :id => 5, :body => "Is this the tiny town from Footloose?", :locked => true } }].to_json] + else + [200, {}, [{ :comment => { :id => 4, :body => "They're having a FIRESALE?" } }].to_json] + end + end stub.get("/users/2/comments") { |env| [200, {}, [{ :comment => { :id => 4, :body => "They're having a FIRESALE?" } }, { :comment => { :id => 5, :body => "Is this the tiny town from Footloose?" } }].to_json] } stub.get("/users/2/comments/5") { |env| [200, {}, { :comment => { :id => 5, :body => "Is this the tiny town from Footloose?" } }.to_json] } stub.get("/users/2/role") { |env| [200, {}, { :id => 2, :body => "User" }.to_json] } @@ -107,11 +113,6 @@ [200, {}, { :organization => { :id => 2, :name => "Bluth Company" } }.to_json] end end - stub.get("/comments") do |env| - if env[:params]["locked"] == "true" - [200, {}, [{ :comment => { :id => 5, :body => "Is this the tiny town from Footloose?", :locked => true } }].to_json] - end - end end end @@ -124,6 +125,7 @@ spawn_model "Foo::Comment" do belongs_to :user parse_root_in_json true + collection_path 'users/:user_id/comments' scope :locked_scope, lambda { where(locked: true) } end spawn_model "Foo::Post" do From d3846ac9b022854b780ab1a9cb20ad3fdbd05271 Mon Sep 17 00:00:00 2001 From: Daniel Turner Date: Fri, 4 Jul 2014 08:11:55 -0500 Subject: [PATCH 4/5] variable scope fix --- lib/her/model/associations/association.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/her/model/associations/association.rb b/lib/her/model/associations/association.rb index 9de23b23..ebebb4b7 100644 --- a/lib/her/model/associations/association.rb +++ b/lib/her/model/associations/association.rb @@ -19,10 +19,12 @@ def initialize(parent, opts = {}) def call_scope(name, *args, &block) parent_id_string = "#{@parent.class.to_s.demodulize.downcase}_#{@parent.class.primary_key}" parent_id = @parent.send(@parent.class.primary_key) - if klass.collection_path[parent_id_string] - with_parent_id = klass.send(:where, parent_id_string => parent_id) + scoped = if klass.collection_path[parent_id_string] + klass.where(parent_id_string => parent_id) + else + klass end - with_parent_id.send(name, *args, &block) + scoped.send(name, *args, &block) end # @private From 1c36fabff2fa293f61e91af17a2218badc3eba6d Mon Sep 17 00:00:00 2001 From: Daniel Turner Date: Fri, 22 Aug 2014 11:49:11 -0500 Subject: [PATCH 5/5] Her::Api::request will strip out params that start with a leading _. Take advantage of the fact when using scopes on associations to prevent the parent id from bing in the params. --- lib/her/model/associations/association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/her/model/associations/association.rb b/lib/her/model/associations/association.rb index ebebb4b7..b99537ff 100644 --- a/lib/her/model/associations/association.rb +++ b/lib/her/model/associations/association.rb @@ -20,7 +20,7 @@ def call_scope(name, *args, &block) parent_id_string = "#{@parent.class.to_s.demodulize.downcase}_#{@parent.class.primary_key}" parent_id = @parent.send(@parent.class.primary_key) scoped = if klass.collection_path[parent_id_string] - klass.where(parent_id_string => parent_id) + klass.where("_#{parent_id_string}" => parent_id) else klass end