Module: Arkenstone::Associations::ClassMethods
- Defined in:
- lib/arkenstone/associations.rb
Instance Method Summary collapse
-
#add_association_method(method_name, &method_definition) ⇒ Object
Adds a method to a class unless that method is already defined.
-
#belongs_to(parent_model_name) ⇒ Object
The opposite of a has_X relationship.
-
#has_and_belongs_to_many(model_klass_name) ⇒ Object
Support for ‘has_and_belongs_to_many` relationship.
-
#has_many(child_model_name, options = {}) ⇒ Object
Creates a One to Many association with the supplied ‘child_model_name`.
-
#has_one(child_model_name, options = {}) ⇒ Object
Similar to ‘has_many` but for a One to One association.
-
#setup_arkenstone_data ⇒ Object
All association data is stored in a hash (@arkenstone_data) on the instance of the class.
Instance Method Details
#add_association_method(method_name, &method_definition) ⇒ Object
Adds a method to a class unless that method is already defined.
308 309 310 |
# File 'lib/arkenstone/associations.rb', line 308 def add_association_method(method_name, &method_definition) define_method method_name, method_definition unless method_defined? method_name end |
#belongs_to(parent_model_name) ⇒ Object
The opposite of a has_X relationship. Allows you to go back up the association tree. Example:
class Hat
belongs_to :llama
end
class Llama
end
Once ‘belongs_to` has been evaluated, the structure of `Hat` will look like this:
class Hat
def llama
#snip
end
end
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/arkenstone/associations.rb', line 196 def belongs_to(parent_model_name) setup_arkenstone_data parent_model_field = "#{parent_model_name}_id" self.arkenstone_attributes = [] unless arkenstone_attributes arkenstone_attributes << parent_model_field.to_sym class_eval("attr_accessor :#{parent_model_field}", __FILE__, __LINE__) # The method for accessing the cached data is `cached_[name]`. If the cache is empty it creates a request to repopulate it from the server. cached_parent_model_name = "cached_#{parent_model_name}" add_association_method cached_parent_model_name do cache = arkenstone_data cache[parent_model_name] = fetch_parent parent_model_name if cache[parent_model_name].nil? cache[parent_model_name] end # The uncached version is the name supplied to belongs_to. It wipes the cache for the association and refetches it. add_association_method parent_model_name.to_s do arkenstone_data[parent_model_name] = nil send cached_parent_model_name end define_method("#{parent_model_name}=") do |parent_instance| send "#{parent_model_field}=".to_sym, parent_instance.id end end |
#has_and_belongs_to_many(model_klass_name) ⇒ Object
Support for ‘has_and_belongs_to_many` relationship
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/arkenstone/associations.rb', line 225 def has_and_belongs_to_many(model_klass_name) # Gather the namespace namespace = to_s.split(/::/) model_klass_name = model_klass_name.to_s.singularize.underscore.to_sym current_klass_name = namespace.pop.underscore.to_sym # Build join class needs join_klass_name = [model_klass_name, current_klass_name].sort.join('_') join_klass_classified = join_klass_name.classify.to_sym join_klass_pluralized = join_klass_name.pluralize namespace = Kernel.const_get(namespace.join('::')) # Create the join class if it doesn't exist already unless namespace.constants.include?(join_klass_classified) join_klass = namespace.const_set(join_klass_classified, Class.new) join_klass.instance_eval { include Arkenstone::Document } # The join class should belong to both foreign sides of the relationship join_klass.send :belongs_to, model_klass_name join_klass.send :belongs_to, current_klass_name end # This class should belong to the join table send(:has_many, join_klass_pluralized.to_sym) unless respond_to?(join_klass_pluralized.to_sym) # These are helper variables for the cached and uncached join `:through` instances model_klass_pluralized = model_klass_name.to_s.pluralize cached_instances_field = "cached_#{model_klass_pluralized}" send :attr_accessor, cached_instances_field.to_sym # Creates a `self.join_through_instances` helper method # # This actually pulls instances of the join model and then maps on the # complimenting foreign key to gather all the foreign join instances # define_method model_klass_pluralized.to_s do current_klass_instance = self # The instance calling this method current_klass_pluralized = current_klass_name.to_s.pluralize # Check for cached joined instances cached_instances = current_klass_instance.send cached_instances_field return cached_instances if cached_instances # Get from joined instances model_klass_instances = send("cached_#{join_klass_pluralized}".to_sym).map(&:"#{model_klass_pluralized}") # Redefine `<<` so that you can something like `beer.tags << new_tag` model_klass_instances.define_singleton_method :<< do |element| # Use built in `push` for `Array.new` push element # Cache the result current_klass_instance.send "#{cached_instances_field}=", self # Add the current class instance in the other side of the join # The equivelant of doing `beer.tags << tag` then `tag.beers << beer` # # Grab the current_klass_instances from element element_current_klass_instances = element.send(current_klass_pluralized) # Push the current_klass_instance to what element currently has element_current_klass_instances.push(current_klass_instance) # Save the new stack of current_klass_instances with element element.send "#{current_klass_pluralized}=", element_current_klass_instances # Return the new array self end model_klass_instances end # This creates a setter helper to set all joined instances on the # opposite side of the foreign join # define_method "#{model_klass_pluralized}=" do |elements| current_klass_instance = self current_klass_instance.send "#{cached_instances_field}=", elements end end |
#has_many(child_model_name, options = {}) ⇒ Object
Creates a One to Many association with the supplied ‘child_model_name`. Example:
class Flea
end
class Llama
has_many :fleas
end
Once ‘has_many` has evaluated, the structure of `Llama` will look like this:
class Llama
url 'http://example.com/llamas'
def cached_fleas
#snip
end
def fleas
#snip
end
def flea_ids
[...] # all the ids of the fleas
end
def add_flea(new_flea)
#snip
end
def remove_flea(flea_to_remove)
#snip
end
end
You can override the url of the association by passing in model_name: ‘something’. This will change the URL it fetches from to use the ‘model_name` instead:
has_many :fleas, model_name: 'bugs'
Will fetch ‘fleas` from `example.com/llamas/:id/bugs.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/arkenstone/associations.rb', line 75 def has_many(child_model_name, = {}) setup_arkenstone_data child_url_fragment = [:model_name] || child_model_name child_class_name = [:class_name] || child_model_name # The method for accessing the cached data is `cached_[name]`. If the cache is empty it creates a request to repopulate it from the server. cached_child_name = "cached_#{child_model_name}" add_association_method cached_child_name do cache = arkenstone_data if cache[child_model_name].nil? child_instances = fetch_children child_class_name, child_url_fragment attach_nested_has_many_resource_methods(child_instances, child_model_name, child_class_name) cache[child_model_name] = child_instances end cache[child_model_name] end # The uncached version is the name supplied to has_many. It wipes the cache for the association and refetches it. add_association_method child_model_name do wipe_arkenstone_cache child_model_name send cached_child_name end # Creates an array of the ids of the child models for quick access. singular = child_model_name.to_s.singularize add_association_method "#{singular}_ids" do (send cached_child_name).map(&:id) end # Add a model to the association with add_[child_model_name]. It performs two network calls, one to add it, then another to refetch the association. add_child_method_name = "add_#{singular}" add_association_method add_child_method_name do |new_child| add_child child_model_name, new_child.id wipe_arkenstone_cache child_model_name send cached_child_name end # Remove a model from the association with remove_[child_model_name]. It performs two network calls, one to add it, then another to refetch the association. remove_child_method_name = "remove_#{singular}" add_association_method remove_child_method_name do |child_to_remove| remove_child child_model_name, child_to_remove.id wipe_arkenstone_cache child_model_name send cached_child_name end end |
#has_one(child_model_name, options = {}) ⇒ Object
Similar to ‘has_many` but for a One to One association. Example:
class Hat
end
class Llama
has_one :hat
end
Once ‘has_one` has evaluated, the structure of `Llama` will look like this:
class Llama
def cached_hat
#snip
end
def hat
#snip
end
def hat=(new_value)
#snip
end
end
If nil is passed into the setter method (‘hat=` in the above example), the association is removed.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/arkenstone/associations.rb', line 147 def has_one(child_model_name, = {}) setup_arkenstone_data child_url_fragment = [:model_name] || child_model_name # The method for accessing the cached single resource is `cached_[name]`. If the value is nil it creates a request to pull the value from the server. cached_child_name = "cached_#{child_model_name}" add_association_method cached_child_name do cache = arkenstone_data cache[child_model_name] = fetch_child child_model_name, child_url_fragment if cache[child_model_name].nil? cache[child_model_name] end # The uncached version is retrieved by wiping the cache for the association, and then re-getting it. add_association_method child_model_name do arkenstone_data[child_model_name] = nil send cached_child_name end # A single association is updated or removed with a setter method. setter_method_name = "#{child_model_name}=" add_association_method setter_method_name do |new_value| if new_value.nil? old_model = send child_model_name remove_child child_model_name, old_model.id wipe_arkenstone_cache child_model_name else add_child child_model_name, new_value.id wipe_arkenstone_cache child_model_name send cached_child_name end end end |
#setup_arkenstone_data ⇒ Object
All association data is stored in a hash (@arkenstone_data) on the instance of the class. Each entry in the hash is keyed off the association name. The value of the hash key is a basic array. This can be wrapped up and extended if (when) more functionality is needed. ‘setup_arkenstone_data` creates the following instance methods on the class:
‘arkenstone_data` - the hash for the association data. Only use this if you’re absolutely 100% sure that you don’t need to get up to date data.
‘wipe_arkenstone_cache` - clears the cache for the association provided
23 24 25 26 27 28 29 30 31 32 |
# File 'lib/arkenstone/associations.rb', line 23 def setup_arkenstone_data define_method('arkenstone_data') do @arkenstone_data = {} if @arkenstone_data.nil? @arkenstone_data end define_method('wipe_arkenstone_cache') do |model_name| arkenstone_data[model_name] = nil end end |