Class: ReactiveResource::Base
- Inherits:
-
ActiveResource::Base
- Object
- ActiveResource::Base
- ReactiveResource::Base
- Extended by:
- Extensions::RelativeConstGet
- Defined in:
- lib/reactive_resource/base.rb
Overview
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 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 Attribute Summary collapse
-
.associations ⇒ Object
Holds all the associations that have been declared for this class.
Class Method Summary collapse
-
.association_prefix(options) ⇒ Object
Generates the URL prefix that the belongs_to parameters and associations refer to.
-
.belongs_to(attribute, options = {}) ⇒ Object
Add a parent-child relationship between
attribute
and this class. -
.belongs_to_associations ⇒ Object
Returns all of the belongs_to associations this class has.
-
.belongs_to_with_parents ⇒ Object
belongs_to in ReactiveResource works a little differently than ActiveRecord.
-
.collection_name ⇒ Object
Override ActiveResource’s
collection_name
to support singular names for singleton resources. -
.collection_path(prefix_options = {}, query_options = nil) ⇒ Object
This method differs from its parent by adding association_prefix into the generated url.
-
.custom_method_collection_url(method_name, options = {}) ⇒ Object
Same as collection_path, except with an extra
method_name
on the end to support custom methods. -
.element_path(id, prefix_options = {}, query_options = nil) ⇒ Object
Same as collection_path, except it adds the ID to the end of the path (unless it’s a singleton resource).
-
.extension ⇒ Object
Returns the extension based on the format (‘.json’, for example), or the empty string if
format
doesn’t specify an extension. -
.find_one(options) ⇒ Object
Active Resource’s find_one does nothing if you don’t pass a
:from
parameter. -
.has_many(attribute, options = {}) ⇒ Object
Add a has_many relationship to another class.
-
.has_one(attribute, options = {}) ⇒ Object
Add a has_one relationship to another class.
-
.inherited(child) ⇒ Object
Fix up the
klass
attribute of all of our associations to point to the new child class instead of the parent class, so class lookup works as expected. -
.parents ⇒ Object
All the classes that this class references in its
belongs_to
, along with their parents, and so on. -
.prefix_associations(options) ⇒ Object
Returns a list of the belongs_to associations we will use to generate the full path for this resource.
-
.prefix_parameters ⇒ Object
Add all of the belongs_to attributes as prefix parameters.
-
.singleton ⇒ Object
Call this method to transform a resource into a ‘singleton’ resource.
-
.singleton? ⇒ Boolean
true
if this resource is a singleton resource,false
otherwise.
Instance Method Summary collapse
-
#encode(options = {}) ⇒ Object
ActiveResource (as of 3.0) assumes that you have a to_x method on ActiveResource::Base for any format ‘x’ that is assigned to the record.
-
#load(attributes, remove_root = false) ⇒ Object
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’ll try to inject them based on the attributes of the object we just used.
-
#save ⇒ Object
In order to support two-way belongs_to associations in a reasonable way, we duplicate all of the prefix options as real attributes (so license.lawyer_id will set both the lawyer_id attribute and the lawyer_id prefix option).
Methods included from Extensions::RelativeConstGet
Class Attribute Details
.associations ⇒ Object
Holds all the associations that have been declared for this class
179 180 181 |
# File 'lib/reactive_resource/base.rb', line 179 def associations @associations end |
Class Method Details
.association_prefix(options) ⇒ Object
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/’.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/reactive_resource/base.rb', line 153 def self.association_prefix() = .dup association_prefix = '' if belongs_to_associations used_associations = prefix_associations() association_prefix = used_associations.map do |association| collection_name = association.associated_class.collection_name if association.associated_class.singleton? collection_name else value = .delete("#{association.attribute}_id".intern) "#{collection_name}/#{value}" end end.join('/') # add trailing slash association_prefix << '/' unless used_associations.empty? end association_prefix end |
.belongs_to(attribute, options = {}) ⇒ Object
Add a parent-child relationship between attribute
and this 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.
211 212 213 |
# File 'lib/reactive_resource/base.rb', line 211 def self.belongs_to(attribute, = {}) self.associations << Association::BelongsToAssociation.new(self, attribute, ) end |
.belongs_to_associations ⇒ Object
Returns all of the belongs_to associations this class has.
216 217 218 |
# File 'lib/reactive_resource/base.rb', line 216 def self.belongs_to_associations self.associations.select {|assoc| assoc.kind_of?(Association::BelongsToAssociation) } end |
.belongs_to_with_parents ⇒ Object
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.
286 287 288 289 290 291 292 |
# File 'lib/reactive_resource/base.rb', line 286 def self.belongs_to_with_parents @belongs_to_with_parents ||= begin ret = belongs_to_associations.map(&:associated_attributes) ret += parents.map(&:belongs_to_with_parents) ret.flatten.uniq end end |
.collection_name ⇒ Object
Override ActiveResource’s collection_name
to support singular names for singleton resources.
46 47 48 49 50 51 52 |
# File 'lib/reactive_resource/base.rb', line 46 def self.collection_name if singleton? element_name else super end end |
.collection_path(prefix_options = {}, query_options = nil) ⇒ Object
This method differs from its parent by adding association_prefix into the generated url. This is needed to support belongs_to associations.
64 65 66 67 |
# File 'lib/reactive_resource/base.rb', line 64 def self.collection_path( = {}, = nil) , = () if .nil? "#{prefix()}#{association_prefix()}#{collection_name}#{extension}#{query_string()}" end |
.custom_method_collection_url(method_name, options = {}) ⇒ Object
Same as collection_path, except with an extra method_name
on the end to support custom methods
71 72 73 74 |
# File 'lib/reactive_resource/base.rb', line 71 def self.custom_method_collection_url(method_name, = {}) , = () "#{prefix()}#{association_prefix()}#{collection_name}/#{method_name}#{extension}#{query_string()}" end |
.element_path(id, prefix_options = {}, query_options = nil) ⇒ Object
Same as collection_path, except it adds the ID to the end of the path (unless it’s a singleton resource)
78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/reactive_resource/base.rb', line 78 def self.element_path(id, = {}, = nil) , = () if .nil? element_path = "#{prefix()}#{association_prefix()}#{collection_name}" # singleton resources don't have an ID if id.present? || !singleton? element_path += "/#{id}" end element_path += "#{extension}#{query_string()}" element_path end |
.extension ⇒ Object
Returns the extension based on the format (‘.json’, for example), or the empty string if format
doesn’t specify an extension
57 58 59 |
# File 'lib/reactive_resource/base.rb', line 57 def self.extension format.extension.blank? ? "" : ".#{format.extension}" end |
.find_one(options) ⇒ Object
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
34 35 36 37 38 39 40 41 42 |
# File 'lib/reactive_resource/base.rb', line 34 def self.find_one() found_object = super() if !found_object && singleton? , = ([:params]) path = element_path(nil, , ) found_object = instantiate_record(format.decode(connection.get(path, headers).body), ) end found_object end |
.has_many(attribute, options = {}) ⇒ Object
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.
199 200 201 |
# File 'lib/reactive_resource/base.rb', line 199 def self.has_many(attribute, = {}) self.associations << Association::HasManyAssociation.new(self, attribute, ) end |
.has_one(attribute, options = {}) ⇒ Object
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.
189 190 191 |
# File 'lib/reactive_resource/base.rb', line 189 def self.has_one(attribute, = {}) self.associations << Association::HasOneAssociation.new(self, attribute, ) end |
.inherited(child) ⇒ Object
Fix up the klass
attribute of all of our associations to point to the new child class instead of the parent class, so class lookup works as expected
223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/reactive_resource/base.rb', line 223 def self.inherited(child) super(child) child.associations = [] associations.each do |association| begin child.associations << association.class.new(child, association.attribute, association.) rescue NameError # assume that they'll fix the association later by manually specifying :class_name in the belongs_to end end end |
.parents ⇒ Object
All the classes that this class references in its belongs_to
, along with their parents, and so on.
296 297 298 |
# File 'lib/reactive_resource/base.rb', line 296 def self.parents @parents ||= belongs_to_associations.map(&:associated_class) end |
.prefix_associations(options) ⇒ Object
Returns a list of the belongs_to associations we will use to generate the full path for this resource.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/reactive_resource/base.rb', line 123 def self.prefix_associations() = .dup used_associations = [] parent_associations = [] # Recurse to add the parent resource hierarchy. For Phone, for # instance, this will add the '/lawyers/:id' part of the URL, # which it knows about from the Address class. parents.each do |parent| parent_associations = parent.prefix_associations() break unless parent_associations.empty? end # The association chain we're following used_association = nil belongs_to_associations.each do |association| if !used_association && (association.associated_class.singleton? || param_value = .delete("#{association.attribute}_id".intern)) # only take the first one used_associations << association break end end parent_associations + used_associations end |
.prefix_parameters ⇒ Object
Add all of the belongs_to attributes as prefix parameters. This is necessary to support nested url generation on our polymorphic associations, because we need some way of getting the attributes at the point where we need to generate the url, and only the prefix options are available for both finds and creates.
112 113 114 115 116 117 118 119 |
# File 'lib/reactive_resource/base.rb', line 112 def self.prefix_parameters if !@prefix_parameters @prefix_parameters = super @prefix_parameters.merge(belongs_to_with_parents.map {|p| "#{p}_id".to_sym}) end @prefix_parameters end |
.singleton ⇒ Object
Call this method to transform a resource into a ‘singleton’ resource. This will fix the paths Active Resource generates for singleton resources. See rails.lighthouseapp.com/projects/8994/tickets/4348-supporting-singleton-resources-in-activeresource for more info.
20 21 22 |
# File 'lib/reactive_resource/base.rb', line 20 def self.singleton self.singleton_resource = true end |
.singleton? ⇒ Boolean
true
if this resource is a singleton resource, false
otherwise
26 27 28 |
# File 'lib/reactive_resource/base.rb', line 26 def self.singleton? self.singleton_resource? end |
Instance Method Details
#encode(options = {}) ⇒ Object
ActiveResource (as of 3.0) assumes that you have a to_x method on ActiveResource::Base for any format ‘x’ that is assigned to the record. This seems weird – the format should take care of encoding, not the object itself. To keep this as stable as possible, we should use to_x if it’s defined, otherwise just delegate to the format’s ‘encode’ function. This is how things worked as of Rails 2.3.
242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/reactive_resource/base.rb', line 242 def encode( = {}) if defined?(ActiveResource::VERSION) && ActiveResource::VERSION::MAJOR == 3 if respond_to?("to_#{self.class.format.extension}") super() else self.class.format.encode(attributes, ) end else super() end end |
#load(attributes, remove_root = false) ⇒ Object
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’ll try to inject them based on the attributes of the object we just used.
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/reactive_resource/base.rb', line 94 def load(attributes, remove_root=false) attributes = attributes.stringify_keys self.class.belongs_to_with_parents.each do |belongs_to_param| attributes["#{belongs_to_param}_id"] ||= ["#{belongs_to_param}_id".intern] # also set prefix attributes as real attributes. Otherwise, # belongs_to attributes will be stripped out of the response # even if we aren't actually using the association. @attributes["#{belongs_to_param}_id"] = attributes["#{belongs_to_param}_id"] end super(attributes, remove_root) end |
#save ⇒ Object
In order to support two-way belongs_to associations in a reasonable way, we duplicate all of the prefix options as real attributes (so license.lawyer_id will set both the lawyer_id attribute and the lawyer_id prefix option). When we’re ready to save, we should take all the parameters that we’re already sending as prefix options and remove them from the model’s attributes so we don’t send duplicates down the wire. This way, if you have an object that belongs_to :lawyer and belongs_to :phone (in that order), setting lawyer_id and phone_id will treat lawyer_id as a prefix option and phone_id as a normal attribute.
265 266 267 268 269 270 271 272 273 |
# File 'lib/reactive_resource/base.rb', line 265 def save if self.class.belongs_to_associations used_attributes = self.class.prefix_associations().map {|association| association.attribute} used_attributes.each do |attribute| attributes.delete("#{attribute}_id".intern) end end super end |