Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions lib/active_resource/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ module Builder
# [:class_name]
# Specify the class name of the association. This class name would
# be used for resolving the association class.
# [:foreign_key]
# Specify the foreign key used for the association. By default, the key is
# inferred from the associated `element_name` class method with an "_id"
# suffix.
# [:primary_key]
# Specify the primary key used for the association. By default, the key is
# inferred from the `primary_key` class method.
#
# ==== Example for [:class_name] - option
# GET /posts/123.json delivers following response body:
Expand Down Expand Up @@ -50,6 +57,13 @@ def has_many(name, options = {})
# [:class_name]
# Specify the class name of the association. This class name would
# be used for resolving the association class.
# [:foreign_key]
# Specify the foreign key used for the association. By default, the key is
# inferred from the associated `element_name` class method with an "_id"
# suffix.
# [:primary_key]
# Specify the primary key used for the association. By default, the key is
# inferred from the `primary_key` class method.
#
# ==== Example for [:class_name] - option
# GET /posts/1.json delivers following response body:
Expand Down Expand Up @@ -141,14 +155,18 @@ def defines_belongs_to_finder_method(reflection)
def defines_has_many_finder_method(reflection)
method_name = reflection.name
ivar_name = :"@#{method_name}"
options = reflection.options

define_method(method_name) do
foreign_key = options.fetch(:foreign_key, "#{self.class.element_name}_id")
primary_key = send(options.fetch(:primary_key, self.class.primary_key))

if instance_variable_defined?(ivar_name)
instance_variable_get(ivar_name)
elsif attributes.include?(method_name)
read_attribute(method_name)
elsif !new_record?
instance_variable_set(ivar_name, reflection.klass.where("#{self.class.element_name}_id": self.id))
instance_variable_set(ivar_name, reflection.klass.where(foreign_key => primary_key))
else
instance_variable_set(ivar_name, self.class.collection_parser.new)
end
Expand All @@ -159,16 +177,20 @@ def defines_has_many_finder_method(reflection)
def defines_has_one_finder_method(reflection)
method_name = reflection.name
ivar_name = :"@#{method_name}"
options = reflection.options

define_method(method_name) do
foreign_key = options.fetch(:foreign_key, "#{self.class.element_name}_id")
primary_key = send(options.fetch(:primary_key, self.class.primary_key))

if instance_variable_defined?(ivar_name)
instance_variable_get(ivar_name)
elsif attributes.include?(method_name)
read_attribute(method_name)
elsif reflection.klass.respond_to?(:singleton_name)
instance_variable_set(ivar_name, reflection.klass.find(params: { "#{self.class.element_name}_id": self.id }))
instance_variable_set(ivar_name, reflection.klass.find(params: { foreign_key => primary_key }))
else
instance_variable_set(ivar_name, reflection.klass.find(:one, from: "/#{self.class.collection_name}/#{self.id}/#{method_name}#{self.class.format_extension}"))
instance_variable_set(ivar_name, reflection.klass.find(:one, from: "/#{self.class.collection_name}/#{primary_key}/#{method_name}#{self.class.format_extension}"))
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/active_resource/associations/builder/has_many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module ActiveResource::Associations::Builder
class HasMany < Association
self.valid_options += [ :primary_key, :foreign_key ]

self.macro = :has_many

def build
Expand Down
2 changes: 2 additions & 0 deletions lib/active_resource/associations/builder/has_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module ActiveResource::Associations::Builder
class HasOne < Association
self.valid_options += [ :primary_key, :foreign_key ]

self.macro = :has_one

def build
Expand Down
83 changes: 83 additions & 0 deletions test/cases/association_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
require "fixtures/person"
require "fixtures/beast"
require "fixtures/customer"
require "fixtures/weather"


class AssociationTest < ActiveSupport::TestCase
def setup
@klass = ActiveResource::Associations::Builder::Association
@reflection = ActiveResource::Reflection::AssociationReflection.new :belongs_to, :customer, {}
@previous_reflections, External::Person.reflections = External::Person.reflections, {}
end

def teardown
External::Person.reflections = @previous_reflections
end

def test_validations_for_instance
object = @klass.new(Person, :customers, {})
Expand Down Expand Up @@ -48,6 +53,28 @@ def test_has_many
assert_equal [ "Related" ], people.map(&:name)
end

def test_has_many_with_primary_key
External::Person.has_many(:people, primary_key: :parent_id)

ActiveResource::HttpMock.respond_to.get "/people.json?person_id=1", {}, { people: [ { id: 2, name: "Related" } ] }.to_json
person = External::Person.new({ parent_id: 1 }, true)

people = person.people

assert_equal [ "Related" ], people.map(&:name)
end

def test_has_many_with_foreign_key
External::Person.has_many(:people, foreign_key: :parent_id)

ActiveResource::HttpMock.respond_to.get "/people.json?parent_id=1", {}, { people: [ { id: 2, name: "Related" } ] }.to_json
person = External::Person.new({ id: 1 }, true)

people = person.people

assert_equal [ "Related" ], people.map(&:name)
end

def test_has_many_chain
External::Person.send(:has_many, :people)

Expand All @@ -68,6 +95,62 @@ def test_has_many_on_new_record
def test_has_one
External::Person.send(:has_one, :customer)
assert_equal 1, External::Person.reflections.select { |name, reflection| reflection.macro.eql?(:has_one) }.count

ActiveResource::HttpMock.respond_to.get "/people/1/customer.json", {}, { person: { id: 2, name: "Customer" } }.to_json
person = External::Person.new({ id: 1 }, true)

customer = person.customer

assert_equal "Customer", customer.name
end

def test_has_one_singleton
External::Person.send(:has_one, :weather)

ActiveResource::HttpMock.respond_to.get "/weather.json?person_id=1", {}, { weather: { id: 1, status: "Sunshine" } }.to_json
person = External::Person.new({ id: 1 }, true)

weather = person.weather

assert_equal "Sunshine", weather.status
end

def test_has_one_with_primary_key
External::Person.send(:has_one, :customer, primary_key: :customer_id)

ActiveResource::HttpMock.respond_to.get "/people/1/customer.json", {}, { person: { id: 2, name: "Customer" } }.to_json
person = External::Person.new({ customer_id: 1 }, true)

customer = person.customer

assert_equal "Customer", customer.name
end

def test_has_one_singleton_with_primary_key
External::Person.send(:has_one, :weather, primary_key: :person_id)

ActiveResource::HttpMock.respond_to.get "/weather.json?person_id=1", {}, { weather: { id: 1, status: "Sunshine" } }.to_json
person = External::Person.new({ person_id: 1 }, true)

weather = person.weather

assert_equal "Sunshine", weather.status
end

def test_has_one_singleton_with_foreign_key
previous_prefix = Weather.prefix
Weather.prefix = "/people/:owner_id/"

External::Person.send(:has_one, :weather, foreign_key: :owner_id)

ActiveResource::HttpMock.respond_to.get "/people/1/weather.json", {}, { weather: { id: 1, status: "Sunshine" } }.to_json
person = External::Person.new({ id: 1 }, true)

weather = person.weather

assert_equal "Sunshine", weather.status
ensure
Weather.prefix = previous_prefix
end

def test_belongs_to
Expand Down
10 changes: 10 additions & 0 deletions test/cases/associations/builder/has_many_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ def test_instance_build
assert_equal :street_address, reflection.name
assert_equal StreetAddress, reflection.klass
end

def test_valid_options
assert @klass.build(Person, :street_address, class_name: "StreetAddress")
assert @klass.build(Person, :street_address, foreign_key: "person_id")
assert @klass.build(Person, :street_address, primary_key: "id")

assert_raise ArgumentError do
@klass.build(Person, :street_address, soo_invalid: true)
end
end
end
10 changes: 10 additions & 0 deletions test/cases/associations/builder/has_one_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ def test_instance_build
assert_equal :inventory, reflection.name
assert_equal Inventory, reflection.klass
end

def test_valid_options
assert @klass.build(Product, :inventory, class_name: "Product")
assert @klass.build(Product, :inventory, foreign_key: "product_id")
assert @klass.build(Product, :inventory, primary_key: "id")

assert_raise ArgumentError do
@klass.build(Product, :inventory, soo_invalid: true)
end
end
end