diff --git a/lib/her/model/associations/association.rb b/lib/her/model/associations/association.rb index 6fc96a87..b99537ff 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 = {}) @@ -15,6 +16,17 @@ 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) + scoped = if klass.collection_path[parent_id_string] + klass.where("_#{parent_id_string}" => parent_id) + else + klass + end + scoped.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 f770a9e5..ffd6c47e 100644 --- a/lib/her/model/associations/association_proxy.rb +++ b/lib/her/model/associations/association_proxy.rb @@ -32,6 +32,11 @@ def method_missing(name, *args, &block) return association.fetch.object_id end + # 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.call_scope(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..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] } @@ -119,6 +125,8 @@ 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 belongs_to :admin, :class_name => 'Foo::User' @@ -228,6 +236,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}")}