Class: WellRested::Base
- Inherits:
-
Object
- Object
- WellRested::Base
- Includes:
- ActiveModel::Serializers::JSON, ActiveModel::Validations, Utils, Utils
- Defined in:
- lib/well_rested/base.rb
Instance Attribute Summary collapse
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
-
#new_record ⇒ Object
Returns the value of attribute new_record.
Class Method Summary collapse
-
.define_schema(*args) ⇒ Object
Define the schema for this resource.
-
.descendant_map ⇒ Object
Create a map of all descendants of Base to lookup classes from names when converting hashes to objects.
-
.descendants ⇒ Object
borrowed from stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby Show all subclasses.
- .fill_path(params) ⇒ Object
-
.find_resource_class(class_name) ⇒ Object
When we are loading a resource from an API call, we will use this method to instantiate classes based on attribute names.
-
.hash_to_objects(hash, from_api = false) ⇒ Object
Convert a hash received from the API into an object or array of objects.
-
.new_from_api(attrs) ⇒ Object
Convenience method for creating an object and calling load_from_api The API should call this method when creating representations of objects that are already persisted.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Equality is defined as having the same attributes.
-
#attributes_for_api ⇒ Object
Return the attributes that we want to send to the server when this resource is saved.
-
#convert_attributes_to_objects ⇒ Object
Convert attribute hashes that represent objects into objects.
-
#handle_errors(received_errors) ⇒ Object
This method is called by API when a hash including ‘errors’ is returned along with an HTTP error code.
-
#id ⇒ Object
Define an actual method for ID.
-
#initialize(attrs = {}) ⇒ Base
constructor
A new instance of Base.
-
#load(attrs_to_load, from_api = false) ⇒ Object
Load this resource from attributes.
-
#load_from_api(attrs) ⇒ Object
Load attributes from the API.
-
#method_missing(method_sym, *args, &block) ⇒ Object
Respond to getter and setter methods for attributes.
-
#new? ⇒ Boolean
Alias of new_record? Apparently used by Rails sometimes.
-
#new_record? ⇒ Boolean
The following 3 methods were copied from active_record/persistence.rb Returns true if this object hasn’t been saved yet – that is, a record for the object doesn’t exist in the data store yet; otherwise, returns false.
-
#path_parameters ⇒ Object
API should use these to generate the path.
-
#persisted? ⇒ Boolean
Returns if the record is persisted, i.e.
-
#read_attribute_for_validation(key) ⇒ Object
Run active_model validations on @attributes hash.
-
#to_key ⇒ Object
Return a key for rails to use for…
-
#to_param ⇒ Object
Return a string form of this object for rails to use in routes.
Constructor Details
#initialize(attrs = {}) ⇒ Base
Returns a new instance of Base.
84 85 86 87 88 |
# File 'lib/well_rested/base.rb', line 84 def initialize(attrs = {}) raise "Attrs must be hash" unless attrs.is_a? Hash self.load(attrs, false) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_sym, *args, &block) ⇒ Object
Respond to getter and setter methods for attributes.
277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/well_rested/base.rb', line 277 def method_missing(method_sym, *args, &block) method = method_sym.to_s # Is this an attribute getter? if args.empty? and attributes.include?(method) attributes[method] # Is it an attribute setter? elsif args.length == 1 and method[method.length-1..method.length-1] == '=' and attributes.include?(attr_name = method[0..method.length-2]) attributes[attr_name] = args.first else super end end |
Instance Attribute Details
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes.
37 38 39 |
# File 'lib/well_rested/base.rb', line 37 def attributes @attributes end |
#new_record ⇒ Object
Returns the value of attribute new_record.
38 39 40 |
# File 'lib/well_rested/base.rb', line 38 def new_record @new_record end |
Class Method Details
.define_schema(*args) ⇒ Object
Define the schema for this resource.
Either takes an array, or a list of arguments which we treat as an array. Each element of the array should be either a symbol or a hash. If it’s a symbol, we create an attribute using the symol as the name and with a null default value. If it’s a hash, we use the keys as attribute names.
- Any values that are hashes, we use to specify further options (currently, the only option is :default).
- Any value that is not a hash is treated as a default.
e.g.
define_schema :x, :y, :z # x, y, and z all default to nil
define_schema :id, :name => 'John' # id defaults to nil, name defaults to 'John'
define_schema :id, :name => { :default => 'John' } # same as above
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/well_rested/base.rb', line 53 def self.define_schema(*args) return schema if args.empty? attrs = args.first.is_a?(Array) ? args.first : args self.schema = {}.with_indifferent_access attrs.each do |attr| if attr.is_a?(Symbol) self.schema[attr] = { :default => nil } elsif attr.is_a?(Hash) attr.each do |k,v| if v.is_a?(Hash) self.schema[k] = v else self.schema[k] = { :default => v } end end end end =begin # Possible alternative to using method_missing: # define getter/setter methods for attributes. @attributes.keys.each do |attr_name| define_method(attr_name) { @attributes[attr_name] } define_method("#{attr_name}=") { |val| @attributes[attr_name] = val } end =end self.schema end |
.descendant_map ⇒ Object
Create a map of all descendants of Base to lookup classes from names when converting hashes to objects.
102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/well_rested/base.rb', line 102 def self.descendant_map return @descendant_map if @descendant_map @descendant_map = {}.with_indifferent_access self.descendants.each do |des| unless des.name.blank? sep_index = des.name.rindex('::') short_name = sep_index ? des.name[sep_index+2..-1] : des.name @descendant_map[short_name] = des end end @descendant_map end |
.descendants ⇒ Object
borrowed from stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby Show all subclasses
97 98 99 |
# File 'lib/well_rested/base.rb', line 97 def self.descendants ObjectSpace.each_object(::Class).select { |klass| klass < self } end |
.fill_path(params) ⇒ Object
210 211 212 |
# File 'lib/well_rested/base.rb', line 210 def self.fill_path(params) API.fill_path(self.path, params) end |
.find_resource_class(class_name) ⇒ Object
When we are loading a resource from an API call, we will use this method to instantiate classes based on attribute names.
172 173 174 175 176 177 |
# File 'lib/well_rested/base.rb', line 172 def self.find_resource_class(class_name) klass = Utils.get_class(class_name) #puts "**** descendant map: #{Base.descendant_map.inspect}" return klass if klass.respond_to?(:new_from_api) Base.descendant_map[class_name] end |
.hash_to_objects(hash, from_api = false) ⇒ Object
Convert a hash received from the API into an object or array of objects. e.g. Base.hash_to_objects(=> {‘name’ => ‘Test’ }) => @attributes={“name”=>“Test”}
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/well_rested/base.rb', line 181 def self.hash_to_objects(hash, from_api = false) hash.each do |k,v| if v.kind_of?(Hash) class_name = k.camelize klass = self.find_resource_class(class_name) if klass hash[k] = from_api ? klass.new_from_api(v) : klass.new(v) end elsif v.kind_of?(Array) class_name = k.to_s.singularize.camelize #puts "**** class_name=#{class_name}" klass = find_resource_class(class_name) if klass #puts "**** class exists, instantiation" hash[k] = v.map do |o| if o.kind_of?(Hash) from_api ? klass.new_from_api(o) : klass.new(o) else o end end else #puts "**** class does not exist" end end end hash end |
.new_from_api(attrs) ⇒ Object
Convenience method for creating an object and calling load_from_api The API should call this method when creating representations of objects that are already persisted.
By default, attributes loaded from the API have new_record set to true. This has implications for Rails form handling. (Rails uses POST for records that it thinks are new, but PUT for records that it thinks are already persisted.)
120 121 122 123 124 |
# File 'lib/well_rested/base.rb', line 120 def self.new_from_api(attrs) obj = self.new obj.load_from_api(attrs) return obj end |
Instance Method Details
#==(other) ⇒ Object
Equality is defined as having the same attributes.
272 273 274 |
# File 'lib/well_rested/base.rb', line 272 def ==(other) other.respond_to?(:attributes) ? (self.attributes == other.attributes) : false end |
#attributes_for_api ⇒ Object
Return the attributes that we want to send to the server when this resource is saved. If a schema is defined, only return elements defined in the schema. Override this for special attribute-handling.
217 218 219 220 221 222 223 |
# File 'lib/well_rested/base.rb', line 217 def attributes_for_api # by default, filter out nil elements hash = objects_to_attributes(@attributes.reject { |k,v| v.nil? }.with_indifferent_access) # and anything not included in the schema hash.reject! { |k,v| !schema.include?(k) } unless schema.nil? hash end |
#convert_attributes_to_objects ⇒ Object
Convert attribute hashes that represent objects into objects
160 161 162 |
# File 'lib/well_rested/base.rb', line 160 def convert_attributes_to_objects self.class.hash_to_objects(attributes, self.class) end |
#handle_errors(received_errors) ⇒ Object
This method is called by API when a hash including ‘errors’ is returned along with an HTTP error code.
165 166 167 168 169 |
# File 'lib/well_rested/base.rb', line 165 def handle_errors(received_errors) received_errors.each do |err| self.errors.add :base, err end end |
#id ⇒ Object
Define an actual method for ID. This is important in Ruby 1.8 where the object_id method is also aliased to id.
91 92 93 |
# File 'lib/well_rested/base.rb', line 91 def id attributes[:id] end |
#load(attrs_to_load, from_api = false) ⇒ Object
Load this resource from attributes. If these attributes were received from the API, true should be passed for from_api. This will ensure any object-specific loading behavior is respected. example:
res = Resource.new
res.load(:name => 'New')
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/well_rested/base.rb', line 131 def load(attrs_to_load, from_api = false) raise "Attrs is not a hash: #{attrs_to_load.inspect}" unless attrs_to_load.kind_of? Hash #puts "*** Warning: loading a resource without a schema (#{self.class})!" if schema.nil? #raise "Tried to load attributes for a resource with no schema (#{self.class})!" if schema.nil? # We mark a record as new if it doesn't come from the API and it doesn't have an ID. self.new_record = !from_api self.new_record = false if attrs_to_load.include?(:id) or attrs_to_load.include?('id') new_attrs = {}.with_indifferent_access # Take default values from schema, but allow arbitrary args to be loaded. # We will address the security issue by filtering in attributes_for_api. schema.each { |key, opts| new_attrs[key] = opts[:default] } unless schema.blank? new_attrs.merge!(attrs_to_load) @attributes = self.class.hash_to_objects(new_attrs, from_api).with_indifferent_access return self end |
#load_from_api(attrs) ⇒ Object
Load attributes from the API. This method exists to be overridden so that attributes created manually can be handled differently from those loaded from the API.
155 156 157 |
# File 'lib/well_rested/base.rb', line 155 def load_from_api(attrs) load(attrs, true) end |
#new? ⇒ Boolean
Alias of new_record? Apparently used by Rails sometimes.
255 256 257 |
# File 'lib/well_rested/base.rb', line 255 def new? self.new_record end |
#new_record? ⇒ Boolean
The following 3 methods were copied from active_record/persistence.rb Returns true if this object hasn’t been saved yet – that is, a record for the object doesn’t exist in the data store yet; otherwise, returns false.
250 251 252 |
# File 'lib/well_rested/base.rb', line 250 def new_record? self.new_record end |
#path_parameters ⇒ Object
API should use these to generate the path. Override this to control how path variables get inserted.
227 228 229 |
# File 'lib/well_rested/base.rb', line 227 def path_parameters objects_to_attributes(@attributes.reject { |k,v| v.nil? }.with_indifferent_access) end |
#persisted? ⇒ Boolean
Returns if the record is persisted, i.e. it’s not a new record and it was not destroyed.
266 267 268 269 |
# File 'lib/well_rested/base.rb', line 266 def persisted? # !(new_record? || destroyed?) !new_record? end |
#read_attribute_for_validation(key) ⇒ Object
Run active_model validations on @attributes hash.
232 233 234 |
# File 'lib/well_rested/base.rb', line 232 def read_attribute_for_validation(key) @attributes[key] end |
#to_key ⇒ Object
Return a key for rails to use for… not sure exaclty what. Should be an array, or nil.
243 244 245 |
# File 'lib/well_rested/base.rb', line 243 def to_key self.id.nil? ? nil : [self.id] end |
#to_param ⇒ Object
Return a string form of this object for rails to use in routes.
237 238 239 |
# File 'lib/well_rested/base.rb', line 237 def to_param self.id.nil? ? nil : self.id.to_s end |