Skip to content

Commit

Permalink
Associations Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
mattbeedle committed Jan 9, 2014
1 parent 48cbe6b commit d4c7c1d
Show file tree
Hide file tree
Showing 16 changed files with 392 additions and 40 deletions.
26 changes: 26 additions & 0 deletions lib/capsule_crm/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ module Associations
included do
include CapsuleCRM::Associations::BelongsTo
include CapsuleCRM::Associations::HasMany

class_attribute :associations
self.associations = {}
end

module ClassMethods
# Public: Gets all the has many associations defined on the class
#
# Returns a Hash
def has_many_associations
select_associations(:has_many)
end

# Public: Get all the belongs to associations defined on the class
#
# Returns a Hash
def belongs_to_associations
select_associations(:belongs_to)
end

def select_associations(macro)
associations.select do |name, association|
association.macro == macro &&
[self, self.parent].include?(association.defined_on)
end
end
end
end
end
22 changes: 12 additions & 10 deletions lib/capsule_crm/associations/belongs_to.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require_relative 'belongs_to_association'

module CapsuleCRM
module Associations
module BelongsTo
Expand Down Expand Up @@ -29,10 +31,12 @@ module ClassMethods
# person.organisation
# => organisation
def belongs_to(association_name, options = {})
foreign_key = options[:foreign_key] || :"#{association_name}_id"
association = CapsuleCRM::Associations::BelongsToAssociation.
new(association_name, self, options)
self.associations[association_name] = association

class_eval do
attribute foreign_key, Integer
attribute association.foreign_key, Integer
end

(class << self; self; end).instance_eval do
Expand All @@ -43,20 +47,18 @@ def belongs_to(association_name, options = {})

define_method association_name do
instance_variable_get(:"@#{association_name}") ||
if self.send(foreign_key)
options[:class_name].constantize.
find(self.send(foreign_key)).tap do |object|
if self.send(association.foreign_key)
association.parent(self).tap do |object|
self.send("#{association_name}=", object)
end
else
nil
end
end

define_method "#{association_name}=" do |associated_object|
instance_variable_set(:"@#{association_name}", associated_object)
id = associated_object ? associated_object.id : nil
self.send "#{foreign_key}=", id
associated_object.tap do |object|
instance_variable_set(:"@#{association_name}", associated_object)
self.send "#{association.foreign_key}=", associated_object.try(:id)
end
end
end
end
Expand Down
69 changes: 69 additions & 0 deletions lib/capsule_crm/associations/belongs_to_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module CapsuleCRM
module Associations
class BelongsToAssociation
attr_reader :association_name, :defined_on, :options

# Public: Initialize a new CapsuleCRM::Associations::BelongsToAssociation
#
# association_name - The Symbol name of the association
# defined_on - The String name of the class that this association
# is defined on
# options - The Hash of association options
# foreign_key - The String foreign_key column name
# class_name - The String name of the parent class
#
# Examples
#
# CapsuleCRM::Associations::BelongsToAssociation.new(
# :person, 'CapsuleCRM::Opportunity', class_name: 'CapsuleCRM::Person'
# )
#
# Returns a CapsuleCRM::Associations::BelongsToAssociation
def initialize(association_name, defined_on, options)
@association_name = association_name
@defined_on = defined_on
@options = options
end

# Public: Build the foreign key column name. If a foreign key name was
# supplied in the options during initialization, then that is returned.
# Otherwise it is inferred from the association name
#
# Returns a String foreign key name
def foreign_key
@foreign_key ||= options[:foreign_key] || "#{association_name}_id"
end

# Public: Find the parent object of the supplied object
#
# object - The object to find the parent for
#
# Examples
#
# association = CapsuleCRM::Associations::BelongsToAssociation.new(
# :person, 'CapsuleCRM::Opportunity', class_name: 'CapsuleCRM::Person'
# )
# object = CapsuleCRM::Opportunity.first
# association.parent(object)
#
# Returns an Object that is on the parent side of the belongs to
# association
def parent(object)
target_klass.find(object.send(foreign_key))
end

# Public: The type of association. Just a convenience method
#
# Returns a Symbol :belongs_to
def macro
:belongs_to
end

private

def target_klass
@target_klass ||= options[:class_name].constantize
end
end
end
end
26 changes: 8 additions & 18 deletions lib/capsule_crm/associations/has_many.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'capsule_crm/associations/has_many_proxy'
require_relative 'has_many_association'
require_relative 'has_many_proxy'

module CapsuleCRM
module Associations
Expand Down Expand Up @@ -34,29 +35,18 @@ module ClassMethods
# organization.people
# => [person]
def has_many(association_name, options = {})
association = CapsuleCRM::Associations::HasManyAssociation.
new(association_name, self, options)
self.associations[association_name] = association

define_method association_name do
instance_variable_get(:"@#{association_name}") ||
CapsuleCRM::Associations::HasManyProxy.new(
self, # parent
options[:class_name].constantize, # target class
options[:class_name].constantize.
send("_for_#{self.class.to_s.demodulize.downcase}", self.id),
options[:source] # source
).tap do |proxy|
instance_variable_set :"@#{association_name}", proxy
end
instance_variable_get :"@#{association_name}"
instance_variable_set(:"@#{association_name}", association.proxy(self))
end

define_method "#{association_name}=" do |associated_objects|
if associated_objects.is_a?(Hash)
associated_objects = Array(options[:class_name].constantize.new(associated_objects[options[:class_name].demodulize.downcase]))
end
instance_variable_set :"@#{association_name}",
CapsuleCRM::Associations::HasManyProxy.new(
self, options[:class_name].constantize,
associated_objects, options[:source]
)
association.proxy(self, associated_objects)
end
end
end
Expand Down
80 changes: 80 additions & 0 deletions lib/capsule_crm/associations/has_many_association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module CapsuleCRM
module Associations
class HasManyAssociation
attr_reader :association_name, :options, :defined_on

# Public: Initialize a new CapsuleCRM::Associations::HasManyAssociation
#
# association_name - The Symbox association name
# defined_on - The String name of the class that this association
# is defined on
# options - The Hash of association options
# :class_name - The String name of the belongs to
# class
# :source - The Symbol name of the accessor method on
# the belongs to class
#
# Examples
#
# CapsuleCRM::Associations::HasManyAssociation.new(
# :opportunities, CapsuleCRM::Person, class_name:
# 'CapsuleCRM::Opportunity, source: :person
# )
#
# Returns a CapsuleCRM::Associations::HasManyAssociation
def initialize(association_name, defined_on, options)
@association_name = association_name
@options = options
@defined_on = defined_on
end

# Public: Build the HasManyProxy object
#
# parent - The instance of the class that the has many assocation is
# defined on
# collection - An optional Array or Hash to use as the target for the
# proxy
#
# Returns a CapsuleCRM::Associations::HasManyProxy
def proxy(parent, collection = nil)
CapsuleCRM::Associations::HasManyProxy.new(
parent, target_klass, build_target(parent, collection), source
)
end

# Public: The type of association. Just a convenience method
#
# Return a Symbol :has_many
def macro
:has_many
end

private

def build_target(parent, collection)
collection.nil? ? target(parent) : collection_to_array(collection)
end

def collection_to_array(collection)
if collection.is_a?(Hash)
Array(target_klass.new(collection[collection.keys.first]))
else
collection
end
end

def target_klass
@target_klass ||= options[:class_name].constantize
end

def target(parent)
target_klass.
send("_for_#{parent.class.to_s.demodulize.downcase}", parent.id)
end

def source
@source ||= options[:source]
end
end
end
end
3 changes: 1 addition & 2 deletions lib/capsule_crm/case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ class Case
include ActiveModel::Validations

include CapsuleCRM::Collection
include CapsuleCRM::Associations::HasMany
include CapsuleCRM::Associations::BelongsTo
include CapsuleCRM::Associations
include CapsuleCRM::Taggable

attribute :id, Integer
Expand Down
3 changes: 0 additions & 3 deletions lib/capsule_crm/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ class Organization < CapsuleCRM::Party
extend ActiveModel::Callbacks
extend ActiveModel::Conversion
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks

include CapsuleCRM::Associations::HasMany
include CapsuleCRM::Collection
include CapsuleCRM::Contactable
include CapsuleCRM::Taggable

attribute :id, Integer
attribute :name, String
Expand Down
2 changes: 1 addition & 1 deletion lib/capsule_crm/party.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class CapsuleCRM::Party

include CapsuleCRM::Attributes
include CapsuleCRM::Taggable
include CapsuleCRM::Associations::HasMany
include CapsuleCRM::Associations

has_many :histories, class_name: 'CapsuleCRM::History', source: :party
has_many :tasks, class_name: 'CapsuleCRM::Task', source: :party
Expand Down
1 change: 0 additions & 1 deletion lib/capsule_crm/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module CapsuleCRM
class Person < CapsuleCRM::Party
include CapsuleCRM::Collection
include CapsuleCRM::Contactable
include CapsuleCRM::Associations::BelongsTo

extend ActiveModel::Naming
include ActiveModel::Conversion
Expand Down
2 changes: 1 addition & 1 deletion lib/capsule_crm/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Task
include ActiveModel::Conversion
include ActiveModel::Validations

include CapsuleCRM::Associations::BelongsTo
include CapsuleCRM::Associations
include CapsuleCRM::Attributes
include CapsuleCRM::Collection

Expand Down
2 changes: 1 addition & 1 deletion lib/capsule_crm/track.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Track
include ActiveModel::Conversion
include ActiveModel::Validations

include CapsuleCRM::Associations::HasMany
include CapsuleCRM::Associations
include CapsuleCRM::Attributes
include CapsuleCRM::Collection

Expand Down
2 changes: 1 addition & 1 deletion lib/capsule_crm/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class User
include ActiveModel::Conversion
include ActiveModel::Validations

include CapsuleCRM::Associations::BelongsTo
include CapsuleCRM::Associations

attribute :username, String
attribute :name, String
Expand Down
Loading

0 comments on commit d4c7c1d

Please sign in to comment.