Skip to content

Commit

Permalink
Doc pass
Browse files Browse the repository at this point in the history
  • Loading branch information
justinweiss committed Jan 14, 2011
1 parent e9a07e6 commit 79d122e
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 48 deletions.
2 changes: 1 addition & 1 deletion lib/reactive_resource.rb
@@ -1,4 +1,4 @@
module ReactiveResource
module ReactiveResource # :nodoc:
autoload :Base, 'reactive_resource/base'
autoload :Association, 'reactive_resource/association'
autoload :Extensions, 'reactive_resource/extensions'
Expand Down
4 changes: 2 additions & 2 deletions lib/reactive_resource/association.rb
@@ -1,5 +1,5 @@
module ReactiveResource
module Association
module ReactiveResource
module Association # :nodoc:
autoload :BelongsToAssociation, 'reactive_resource/association/belongs_to_association'
autoload :HasManyAssociation, 'reactive_resource/association/has_many_association'
autoload :HasOneAssociation, 'reactive_resource/association/has_one_association'
Expand Down
24 changes: 19 additions & 5 deletions lib/reactive_resource/association/belongs_to_association.rb
@@ -1,9 +1,20 @@
module ReactiveResource
module Association
# Represents and resolves a belongs_to association.
class BelongsToAssociation

attr_reader :klass, :attribute, :options

# The class this association is attached to
attr_reader :klass

# The attribute name this association represents
attr_reader :attribute

# additional options passed in when the association was created
attr_reader :options

# Returns the class name of the target of the association. Based
# off of +attribute+ unless +class_name+ was passed in the
# +options+ hash.
def associated_class
if options[:class_name]
options[:class_name].constantize
Expand All @@ -22,6 +33,8 @@ def associated_attributes
attributes.uniq
end

# Called when this assocation is referenced. Finds and returns
# the target of this association.
def resolve_relationship(object)
parent_params = object.prefix_options.dup
parent_params.delete("#{attribute}_id".intern)
Expand All @@ -32,9 +45,9 @@ def resolve_relationship(object)
# these objects a bit more straightforward. If the attribute name
# is +lawyer+, it will add:
#
# * lawyer: returns the actual lawyer object (after doing a web request)
# * lawyer_id: returns the lawyer id
# * lawyer_id=: sets the lawyer id
# [lawyer] returns the actual lawyer object (after doing a web request)
# [lawyer_id] returns the lawyer id
# [lawyer_id=] sets the lawyer id
def add_helper_methods(klass, attribute)
association = self

Expand Down Expand Up @@ -70,6 +83,7 @@ def add_helper_methods(klass, attribute)
end
end

# Create a new belongs_to association.
def initialize(klass, attribute, options)
@klass = klass
@attribute = attribute
Expand Down
24 changes: 19 additions & 5 deletions lib/reactive_resource/association/has_many_association.rb
@@ -1,9 +1,20 @@
module ReactiveResource
module Association
# Represents and resolves a has_many association.
class HasManyAssociation

attr_reader :klass, :attribute, :options

# The class this association is attached to
attr_reader :klass

# The attribute name this association represents
attr_reader :attribute

# additional options passed in when the association was created
attr_reader :options

# Returns the class name of the target of the association. Based
# off of +attribute+ unless +class_name+ was passed in the
# +options+ hash.
def associated_class
if options[:class_name]
options[:class_name].constantize
Expand All @@ -12,16 +23,18 @@ def associated_class
end
end

# Called when this assocation is referenced. Finds and returns
# the targets of this association.
def resolve_relationship(object)
id_attribute = "#{klass.name.split("::").last.underscore}_id"
associated_class.find(:all, :params => object.prefix_options.merge(id_attribute => object.id))
end

# Adds methods for belongs_to associations, to make dealing with
# Adds methods for has_many associations, to make dealing with
# these objects a bit more straightforward. If the attribute name
# is +lawyers+, it will add:
#
# * lawyers: returns the associated lawyers
# [lawyers] returns the associated lawyers
def add_helper_methods(klass, attribute)
association = self
klass.class_eval do
Expand All @@ -35,7 +48,8 @@ def add_helper_methods(klass, attribute)
end
end
end


# Create a new has_many association.
def initialize(klass, attribute, options)
@klass = klass
@attribute = attribute
Expand Down
22 changes: 18 additions & 4 deletions lib/reactive_resource/association/has_one_association.rb
@@ -1,9 +1,20 @@
module ReactiveResource
module Association
# Represents and resolves a has_one association
class HasOneAssociation

attr_reader :klass, :attribute, :options

# The class this association is attached to
attr_reader :klass

# The attribute name this association represents
attr_reader :attribute

# additional options passed in when the association was created
attr_reader :options

# Returns the class name of the target of the association. Based
# off of +attribute+ unless +class_name+ was passed in the
# +options+ hash.
def associated_class
if options[:class_name]
options[:class_name].constantize
Expand All @@ -12,6 +23,8 @@ def associated_class
end
end

# Called when this assocation is referenced. Finds and returns
# the target of this association.
def resolve_relationship(object)
id_attribute = "#{klass.name.split("::").last.underscore}_id"
associated_class.find(:one, :params => object.prefix_options.merge(id_attribute => object.id))
Expand All @@ -21,7 +34,7 @@ def resolve_relationship(object)
# these objects a bit more straightforward. If the attribute name
# is +headshot+, it will add:
#
# * headshot: returns the associated headshot
# [headshot] returns the associated headshot
def add_helper_methods(klass, attribute)
association = self
klass.class_eval do
Expand All @@ -35,7 +48,8 @@ def add_helper_methods(klass, attribute)
end
end
end


# Create a new has_one association.
def initialize(klass, attribute, options)
@klass = klass
@attribute = attribute
Expand Down
87 changes: 57 additions & 30 deletions lib/reactive_resource/base.rb
Expand Up @@ -3,9 +3,10 @@
module ReactiveResource

# The class that all ReactiveResourse resources should inherit
# from. This class fixes and patches over a lot of the broken stuff in
# Active Resource, and the differences between the client-side Rails
# REST stuff and the server-side Rails REST stuff.
# from. This class fixes and patches over a lot of the broken stuff
# in Active Resource, and smoothes out the differences between the
# client-side Rails REST stuff and the server-side Rails REST stuff.
# It also adds support for ActiveRecord-like associations.
class Base < ActiveResource::Base
extend Extensions::RelativeConstGet
# Call this method to transform a resource into a 'singleton'
Expand All @@ -17,15 +18,16 @@ def self.singleton
write_inheritable_attribute(:singleton, true)
end

# +true+ if this resource is a singleton resource
# +true+ if this resource is a singleton resource, +false+
# otherwise
def self.singleton?
read_inheritable_attribute(:singleton)
end

# Active Resource's find_one is broken if you don't pass a :from,
# which makes absolutely no sense if you're working with a singleton
# model and trying to generate URLs that Rails' REST support can
# recognize. And thus we come to this:
# Active Resource's find_one does nothing if you don't pass a
# +:from+ parameter. This doesn't make sense if you're dealing
# with a singleton resource, so if we don't get anything back from
# +find_one+, try hitting the element path directly
def self.find_one(options)
found_object = super(options)
if !found_object && singleton?
Expand All @@ -36,8 +38,8 @@ def self.find_one(options)
found_object
end

# Override ActiveResource, because collection name is singular for
# singleton resources.
# Override ActiveResource's +collection_name+ to support singular
# names for singleton resources.
def self.collection_name
if singleton?
element_name
Expand All @@ -46,6 +48,9 @@ def self.collection_name
end
end

# Returns the extension based on the format ('.json', for
# example), or the empty string if +format+ doesn't specify an
# extension
def self.extension
format.extension.blank? ? "" : ".#{format.extension}"
end
Expand All @@ -58,14 +63,15 @@ def self.collection_path(prefix_options = {}, query_options = nil)
"#{prefix(prefix_options)}#{association_prefix(prefix_options)}#{collection_name}#{extension}#{query_string(query_options)}"
end

# Same as collection_path, except with an extra +method_name+
# Same as collection_path, except with an extra +method_name+ on
# the end to support custom methods
def self.custom_method_collection_url(method_name, options = {})
prefix_options, query_options = split_options(options)
"#{prefix(prefix_options)}#{association_prefix(prefix_options)}#{collection_name}/#{method_name}#{extension}#{query_string(query_options)}"
end

# This is overridden to support nested urls for belongs_to
# associations and removing IDs for singleton resources
# Same as collection_path, except it adds the ID to the end of the
# path (unless it's a singleton resource)
def self.element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
element_path = "#{prefix(prefix_options)}#{association_prefix(prefix_options)}#{collection_name}"
Expand All @@ -80,9 +86,8 @@ def self.element_path(id, prefix_options = {}, query_options = nil)

# It's kind of redundant to have the server return the foreign
# keys corresponding to the belongs_to associations (since they'll
# be in the URL anyway), so we have to try to inject them based on
# the current object's attributes. Otherwise, we'll lose these
# keys after we save an object.
# be in the URL anyway), so we'll try to inject them based on the
# attributes of the object we just used.
def load(attributes)
self.class.belongs_to_with_parents.each do |belongs_to_param|
attributes["#{belongs_to_param}_id".intern] ||= prefix_options["#{belongs_to_param}_id".intern]
Expand All @@ -104,9 +109,10 @@ def self.prefix_parameters
@prefix_parameters
end

# Necessary to support polymorphic nested resources. For example, a
# license with params :lawyer_id => 2 will return 'lawyers/2/' and a
# phone with params :address_id => 2, :lawyer_id => 3 will return
# Generates the URL prefix that the belongs_to parameters and
# associations refer to. For example, a license with params
# :lawyer_id => 2 will return 'lawyers/2/' and a phone with params
# :address_id => 2, :lawyer_id => 3 will return
# 'lawyers/3/addresses/2/'.
def self.association_prefix(options)
options = options.dup
Expand All @@ -131,23 +137,39 @@ def self.association_prefix(options)
end

class << self
# Holds all the associations that have been declared for this class
attr_accessor :associations
end
self.associations = []

# Add a has_one relationship to another class.
# Add a has_one relationship to another class. +options+ is a hash
# of extra parameters:
#
# [:class_name] Override the class name of the target of the
# association. By default, this is based on the
# attribute name.
def self.has_one(attribute, options = {})
self.associations << Association::HasOneAssociation.new(self, attribute, options)
end

# Add a has_many relationship to another class.
# Add a has_many relationship to another class. +options+ is a hash
# of extra parameters:
#
# [:class_name] Override the class name of the target of the
# association. By default, this is based on the
# attribute name.
def self.has_many(attribute, options = {})
self.associations << Association::HasManyAssociation.new(self, attribute, options)
end

# Add a parent-child relationship between +attribute+ and this
# class. This allows parameters like +attribute_id+ to contribute to
# generating nested urls.
# class. This allows parameters like +attribute_id+ to contribute
# to generating nested urls. +options+ is a hash of extra
# parameters:
#
# [:class_name] Override the class name of the target of the
# association. By default, this is based on the
# attribute name.
def self.belongs_to(attribute, options = {})
self.associations << Association::BelongsToAssociation.new(self, attribute, options)
end
Expand All @@ -171,13 +193,18 @@ def self.inherited(child)
end
end
end

# merges in all of this class' associated classes' belongs_to
# associations, so we can handle deeply nested routes. So, for
# instance, if we have phone => address => lawyer, phone will look
# for address' belongs_to associations and merge them in. This
# allows us to have both lawyer_id and address_id at url generation
# time.

# belongs_to in ReactiveResource works a little differently than
# ActiveRecord. Because we have to deal with full class hierachies
# in order to generate the full URL (as mentioned in
# association_prefix), we have to treat the belongs_to
# associations on objects that this object belongs_to as if they
# exist on this object itself. This method merges in all of this
# class' associated classes' belongs_to associations, so we can
# handle deeply nested routes. So, for instance, if we have phone
# \=> address => lawyer, phone will look for address' belongs_to
# associations and merge them in. This allows us to have both
# lawyer_id and address_id at url generation time.
def self.belongs_to_with_parents
belongs_to_associations.map(&:associated_attributes).flatten.uniq
end
Expand Down
2 changes: 1 addition & 1 deletion lib/reactive_resource/extensions.rb
@@ -1,3 +1,3 @@
module Extensions
module Extensions # :nodoc:
autoload :RelativeConstGet, 'reactive_resource/extensions/relative_const_get'
end
3 changes: 3 additions & 0 deletions lib/reactive_resource/extensions/relative_const_get.rb
@@ -1,4 +1,7 @@
module Extensions
# Supplies RelativeConstGet#relative_const_get, which is like +const_get+, except it
# attempts to resolve using the current class's module, rather than
# the class's scope itself.
module RelativeConstGet

# Finds the constant with name +name+, relative to the calling
Expand Down
1 change: 1 addition & 0 deletions lib/reactive_resource/version.rb
@@ -1,3 +1,4 @@
module ReactiveResource
# The current version of ReactiveResource
VERSION = "0.0.1"
end

0 comments on commit 79d122e

Please sign in to comment.