Class: Model
- Inherits:
-
SiteRecord
- Object
- AbstractRecord
- MongoRecord
- SiteRecord
- Model
- Extended by:
- Forwardable
- Defined in:
- lib/yodel/models/core/model/model.rb
Constant Summary
Constants inherited from AbstractRecord
AbstractRecord::CALLBACKS, AbstractRecord::FIELD_CALLBACKS, AbstractRecord::ORDERS
Instance Attribute Summary collapse
-
#record_class ⇒ Object
readonly
Returns the value of attribute record_class.
-
#unscoped ⇒ Object
readonly
Returns the value of attribute unscoped.
Attributes inherited from SiteRecord
Attributes inherited from AbstractRecord
#changed, #errors, #stash, #typecast, #values
Instance Method Summary collapse
-
#[](name) ⇒ Object
Simple lookup operator for models that have records with unique names.
-
#add_embed_many(name, options = {}, &block) ⇒ Object
TODO: modify versions of the association methods.
- #add_embed_one(name, options = {}, &block) ⇒ Object
-
#add_field(name, type, options = {}) ⇒ Object
TODO: ensure field name != a public method name.
- #add_index(name, *fields) ⇒ Object
- #add_many(name, options = {}) ⇒ Object
-
#add_mixin(model) ⇒ Object
Add a new mixin to this model.
- #add_one(name, options = {}) ⇒ Object
- #all_record_fields ⇒ Object
- #allowed_child?(other_model) ⇒ Boolean
-
#allowed_children_and_descendants ⇒ Object
—————————————- Admin interface —————————————-.
-
#allowed_parent?(other_model) ⇒ Boolean
Based on the list of allowed parents, returns true if the supplied model is a descendant of a valid parent of this model.
-
#ancestors ⇒ Object
—————————————- Hierarchy —————————————-.
-
#create_model(name, &block) ⇒ Object
Create a new model which inherits from the current model.
-
#deep_stringify_keys(hash) ⇒ Object
TODO: remove copy of this method when abstract_model is mixed in.
-
#destroy ⇒ Object
Destroys all records which are instances of this model, removes a reference to the model from the parent site, and repeats for any child models of the model.
-
#initialize(site, values = {}) ⇒ Model
constructor
A new instance of Model.
-
#load(site, values) ⇒ Object
Load a record from a mongo document.
-
#modify(&block) ⇒ Object
—————————————- Migrations —————————————- Convenience method for migrations, so modifications can be specified with site.model_name.modify { field … etc. }.
- #modify_field(name, options = {}, &block) ⇒ Object
- #new(values = {}) ⇒ Object
-
#parents_and_mixins ⇒ Object
Combine the full set of parents and mixins in a way that doesn’t duplicate models if mixins would cause a duplicate, and maintains the correct position of mixins in the inheritance tree for this model, any parents, and any mixins (and their mixins).
- #query_association?(options) ⇒ Boolean
- #remove_embed_many(name) ⇒ Object
- #remove_embed_one(name) ⇒ Object
- #remove_field(name) ⇒ Object
- #remove_index(name) ⇒ Object
- #remove_many(name) ⇒ Object
-
#remove_mixin(model) ⇒ Object
Remove a mixin from this model.
- #remove_one(name) ⇒ Object
-
#root ⇒ Object
Scope to retrieve the first (or only) root record of a model under a site, e.g Page.root(site) will retrieve the root page of a site.
-
#roots ⇒ Object
Scope to retrieve all root records of a model type under a site, e.g Groups.roots(site).
- #run_record_after_create_callbacks(record) ⇒ Object
- #run_record_after_destroy_callbacks(record) ⇒ Object
- #run_record_after_save_callbacks(record) ⇒ Object
- #run_record_after_update_callbacks(record) ⇒ Object
- #run_record_after_validation_callbacks(record) ⇒ Object
- #run_record_before_create_callbacks(record) ⇒ Object
- #run_record_before_destroy_callbacks(record) ⇒ Object
- #run_record_before_save_callbacks(record) ⇒ Object
- #run_record_before_update_callbacks(record) ⇒ Object
-
#run_record_before_validation_callbacks(record) ⇒ Object
—————————————- Callbacks —————————————- TODO: use loops like in abstract record to write these functions.
- #to_str ⇒ Object
-
#user_allowed_to?(user, action, record) ⇒ Boolean
—————————————- Permissions —————————————-.
- #user_allowed_to_create?(user, record) ⇒ Boolean
- #user_allowed_to_delete?(user, record) ⇒ Boolean
- #user_allowed_to_update?(user, record) ⇒ Boolean
- #user_allowed_to_view?(user, record) ⇒ Boolean
- #valid_child?(other_model) ⇒ Boolean
-
#valid_children ⇒ Object
Returns an array of all allowed children and descendants of those children.
Methods inherited from SiteRecord
#default_values, #inspect_hash, #perform_reload, #prepare_reload_params, #site_id
Methods included from SiteModel
Methods included from MongoModel
Methods included from AbstractModel
#embed_many, #embed_one, #field, #fields, #many, #one
Methods inherited from MongoRecord
#collection, #default_values, #fields, #id, #increment!, #inspect_hash, #load_from_mongo, #load_mongo_document, #perform_destroy, #perform_reload, #perform_save, #set_id
Methods inherited from AbstractRecord
#changed!, #changed?, #clear_key, #default_values, #destroyed?, #eql?, #errors?, #field, #field?, #field_was, #fields, #from_json, #get, #get_meta, #get_raw, #hash, #id, #increment!, inherited, #inspect, #inspect_hash, #inspect_value, #method_missing, #new?, #prepare_reload_params, #present?, #reload, #save, #save_without_validation, #search_terms, #set, #set_meta, #set_raw, #to_json, #trigger_field_callback, #update, #valid?
Constructor Details
#initialize(site, values = {}) ⇒ Model
Returns a new instance of Model.
52 53 54 55 56 57 58 |
# File 'lib/yodel/models/core/model/model.rb', line 52 def initialize(site, values={}) @cached_records_by_name = {} super @unscoped = Record.scoped(site, self) @scope = Record.scoped(site, self, 'model' => get_raw('descendants')) @record_class = Object.module_eval(get_raw('record_class_name')) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class AbstractRecord
Instance Attribute Details
#record_class ⇒ Object (readonly)
Returns the value of attribute record_class.
7 8 9 |
# File 'lib/yodel/models/core/model/model.rb', line 7 def record_class @record_class end |
#unscoped ⇒ Object (readonly)
Returns the value of attribute unscoped.
7 8 9 |
# File 'lib/yodel/models/core/model/model.rb', line 7 def unscoped @unscoped end |
Instance Method Details
#[](name) ⇒ Object
Simple lookup operator for models that have records with unique names. Used as if the model object was a hash: site.emails
147 148 149 150 151 152 153 154 |
# File 'lib/yodel/models/core/model/model.rb', line 147 def [](name) unless @cached_records_by_name.key?(name) record = self.where(name: name).first @cached_records_by_name[name] = record site.cached_records[record.id] = record unless record.nil? end @cached_records_by_name[name] end |
#add_embed_many(name, options = {}, &block) ⇒ Object
TODO: modify versions of the association methods
307 308 309 310 |
# File 'lib/yodel/models/core/model/model.rb', line 307 def (name, ={}, &block) = add_field(name, 'many_embedded', ) .instance_exec(, &block) if block_given? end |
#add_embed_one(name, options = {}, &block) ⇒ Object
316 317 318 319 |
# File 'lib/yodel/models/core/model/model.rb', line 316 def (name, ={}, &block) = add_field(name, 'one_embedded', ) .instance_exec(, &block) if block_given? end |
#add_field(name, type, options = {}) ⇒ Object
TODO: ensure field name != a public method name
269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/yodel/models/core/model/model.rb', line 269 def add_field(name, type, ={}) name = name.to_s # preconditions raise InvalidModelField.new("Duplicate field name") if record_fields.key?(name) raise InvalidModelField.new("Type must be a known yodel field type") unless valid_type?(type) raise InvalidModelField.new("Field name cannot start with an underscore") if name.start_with?('_') # add the field to the model and subclasses field_type = Field.field_from_type(type.to_s) field = field_type.new(name, deep_stringify_keys(.merge(type: type.to_s))) RecordIndex.add_index_for_field(self, field) if field.index? record_fields[name] = field end |
#add_index(name, *fields) ⇒ Object
347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/yodel/models/core/model/model.rb', line 347 def add_index(name, *fields) raise InvalidIndex, 'Indexes must be built on at least one field' if fields.empty? spec = fields.collect do |field| if field.is_a?(Array) [field.first.to_s, (field.last == :desc) ? Mongo::DESCENDING : Mongo::ASCENDING] else [field.to_s, Mongo::ASCENDING] end end RecordIndex.add_index_for_model(self, name, spec) indexes << name end |
#add_many(name, options = {}) ⇒ Object
325 326 327 328 |
# File 'lib/yodel/models/core/model/model.rb', line 325 def add_many(name, ={}) type = query_association?() ? 'many_query' : 'many_store' add_field(name, type, ) end |
#add_mixin(model) ⇒ Object
Add a new mixin to this model
397 398 399 400 401 402 403 404 405 |
# File 'lib/yodel/models/core/model/model.rb', line 397 def add_mixin(model) raise InvalidMixin.new("#{model.name} already mixed in to this model") if mixins.include?(model) raise InvalidMixin.new("Mixin cannot be a parent") if ancestors.include?(model) # for all intents and purposes, by mixing in a model, we are a subtype of that model model.add_descendant(self) mixins << model save end |
#add_one(name, options = {}) ⇒ Object
334 335 336 337 |
# File 'lib/yodel/models/core/model/model.rb', line 334 def add_one(name, ={}) type = query_association?() ? 'one_query' : 'one_store' add_field(name, type, ) end |
#all_record_fields ⇒ Object
181 182 183 184 185 |
# File 'lib/yodel/models/core/model/model.rb', line 181 def all_record_fields parents_and_mixins.each_with_object({}) do |ancestor, fields| fields.merge! ancestor.record_fields # FIXME: should this be record_fields or all_record_fields? end end |
#allowed_child?(other_model) ⇒ Boolean
195 196 197 |
# File 'lib/yodel/models/core/model/model.rb', line 195 def allowed_child?(other_model) allowed_children_and_descendants.include?(other_model) end |
#allowed_children_and_descendants ⇒ Object
Admin interface
191 192 193 |
# File 'lib/yodel/models/core/model/model.rb', line 191 def allowed_children_and_descendants allowed_children.collect(&:descendants).flatten.uniq end |
#allowed_parent?(other_model) ⇒ Boolean
Based on the list of allowed parents, returns true if the supplied model is a descendant of a valid parent of this model.
201 202 203 204 |
# File 'lib/yodel/models/core/model/model.rb', line 201 def allowed_parent?(other_model) other_model_ancestors = other_model.ancestors.to_a allowed_parents.any? {|parent| other_model_ancestors.include?(parent)} end |
#ancestors ⇒ Object
Hierarchy
160 161 162 163 164 165 166 167 168 |
# File 'lib/yodel/models/core/model/model.rb', line 160 def ancestors next_parent = self Enumerator.new do |models| while next_parent models.yield next_parent next_parent = next_parent.parent end end end |
#create_model(name, &block) ⇒ Object
Create a new model which inherits from the current model. If supplied, a block is run and passed a reference to the new model.
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/yodel/models/core/model/model.rb', line 367 def create_model(name, &block) name = name.to_s.tableize raise "Model name '#{name}' is not unique" if site.model_types.key?(name) # create a new instance of model child = self.class.new(site) child.name = name.camelcase.singularize child.parent = self # inherited fields fields.each do |name, field| child.set(name, get(name)) if field.inherited? end # insert the model in to the site models list class_name = name.classify site.model_types[name] = child.id site.model_plural_names[class_name] = name site.save # append the model to ancestor descendant lists (these are used in queries to # restrict the type of records returned, e.g pages.all => _model: ['Page', ...] child.tap do |child| child.add_descendant(child) child.instance_exec(child, &block) if block_given? child.save end end |
#deep_stringify_keys(hash) ⇒ Object
TODO: remove copy of this method when abstract_model is mixed in
298 299 300 301 302 |
# File 'lib/yodel/models/core/model/model.rb', line 298 def deep_stringify_keys(hash) hash.each_with_object({}) do |(key, value), new_hash| new_hash[key.to_s] = (value.respond_to?(:to_hash) ? deep_stringify_keys(value) : value) end end |
#destroy ⇒ Object
Destroys all records which are instances of this model, removes a reference to the model from the parent site, and repeats for any child models of the model.
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/yodel/models/core/model/model.rb', line 416 def destroy # remove this model from the model tree parent.try(:remove_descendant, self) mixins.each {|mixin| mixin.remove_descendant(self)} # destroy model subclasses, and all record instances children.each(&:destroy) all.each(&:destroy) # remove the association between the site and this model site.model_types.delete(name.underscore.pluralize) site.model_plural_names.delete(name) site.save # remove any remaining indexes indexes.each do |name| RecordIndex.remove_index_for_model(self, name) end record_fields.each do |name, field| RecordIndex.remove_index_for_field(self, field) if field.index? end # destroy the model record super end |
#load(site, values) ⇒ Object
Load a record from a mongo document. If this model is not the model of the record, the appropriate model is found and used instead.
120 121 122 123 124 125 126 127 |
# File 'lib/yodel/models/core/model/model.rb', line 120 def load(site, values) return nil if values.nil? if values['model'] != id site.models.find(values['model']).load(site, values) else record_class.new(self, site, values, false) end end |
#modify(&block) ⇒ Object
Migrations
Convenience method for migrations, so modifications can be specified with site.model_name.modify { field … etc. }
262 263 264 265 |
# File 'lib/yodel/models/core/model/model.rb', line 262 def modify(&block) instance_eval &block save end |
#modify_field(name, options = {}, &block) ⇒ Object
290 291 292 293 294 295 |
# File 'lib/yodel/models/core/model/model.rb', line 290 def modify_field(name, ={}, &block) field = record_fields[name.to_s] field. = field..dup.merge(deep_stringify_keys()) field.instance_exec(field, &block) if block_given? changed!('record_fields') end |
#new(values = {}) ⇒ Object
129 130 131 |
# File 'lib/yodel/models/core/model/model.rb', line 129 def new(values={}) record_class.new(self, site).tap {|record| record.update(values, false)} end |
#parents_and_mixins ⇒ Object
Combine the full set of parents and mixins in a way that doesn’t duplicate models if mixins would cause a duplicate, and maintains the correct position of mixins in the inheritance tree for this model, any parents, and any mixins (and their mixins)
173 174 175 176 177 178 179 |
# File 'lib/yodel/models/core/model/model.rb', line 173 def parents_and_mixins models = parent.try(:parents_and_mixins) || [] mixins.each do |mixin_model| models |= mixin_model.parents_and_mixins end models << self end |
#query_association?(options) ⇒ Boolean
343 344 345 |
# File 'lib/yodel/models/core/model/model.rb', line 343 def query_association?() [:store] == false || [:foreign_key, :extends, :through].any? {|opt| [opt].present?} end |
#remove_embed_many(name) ⇒ Object
312 313 314 |
# File 'lib/yodel/models/core/model/model.rb', line 312 def (name) remove_field(name) end |
#remove_embed_one(name) ⇒ Object
321 322 323 |
# File 'lib/yodel/models/core/model/model.rb', line 321 def (name) remove_field(name) end |
#remove_field(name) ⇒ Object
284 285 286 287 288 |
# File 'lib/yodel/models/core/model/model.rb', line 284 def remove_field(name) field = record_fields.delete(name.to_s) raise InvalidModelField.new("Unknown field name") if field.nil? RecordIndex.remove_index_for_field(self, field) if field.index? end |
#remove_index(name) ⇒ Object
360 361 362 363 |
# File 'lib/yodel/models/core/model/model.rb', line 360 def remove_index(name) RecordIndex.remove_index_for_model(self, name) indexes.delete(name) end |
#remove_many(name) ⇒ Object
330 331 332 |
# File 'lib/yodel/models/core/model/model.rb', line 330 def remove_many(name) remove_field(name) end |
#remove_mixin(model) ⇒ Object
Remove a mixin from this model
408 409 410 411 412 |
# File 'lib/yodel/models/core/model/model.rb', line 408 def remove_mixin(model) model.remove_descendant(self) mixins.delete(model) save end |
#remove_one(name) ⇒ Object
339 340 341 |
# File 'lib/yodel/models/core/model/model.rb', line 339 def remove_one(name) remove_field(name) end |
#root ⇒ Object
Scope to retrieve the first (or only) root record of a model under a site, e.g Page.root(site) will retrieve the root page of a site
141 142 143 |
# File 'lib/yodel/models/core/model/model.rb', line 141 def root self.where(parent: nil).order('index asc').first end |
#roots ⇒ Object
Scope to retrieve all root records of a model type under a site, e.g Groups.roots(site). Returns all records with a nil parent.
135 136 137 |
# File 'lib/yodel/models/core/model/model.rb', line 135 def roots self.where(parent: nil).order('index asc') end |
#run_record_after_create_callbacks(record) ⇒ Object
89 90 91 |
# File 'lib/yodel/models/core/model/model.rb', line 89 def run_record_after_create_callbacks(record) record_after_create_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_after_destroy_callbacks(record) ⇒ Object
105 106 107 |
# File 'lib/yodel/models/core/model/model.rb', line 105 def run_record_after_destroy_callbacks(record) record_after_destroy_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_after_save_callbacks(record) ⇒ Object
81 82 83 |
# File 'lib/yodel/models/core/model/model.rb', line 81 def run_record_after_save_callbacks(record) record_after_save_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_after_update_callbacks(record) ⇒ Object
97 98 99 |
# File 'lib/yodel/models/core/model/model.rb', line 97 def run_record_after_update_callbacks(record) record_after_update_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_after_validation_callbacks(record) ⇒ Object
73 74 75 |
# File 'lib/yodel/models/core/model/model.rb', line 73 def run_record_after_validation_callbacks(record) record_after_validation_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_before_create_callbacks(record) ⇒ Object
85 86 87 |
# File 'lib/yodel/models/core/model/model.rb', line 85 def run_record_before_create_callbacks(record) record_before_create_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_before_destroy_callbacks(record) ⇒ Object
101 102 103 |
# File 'lib/yodel/models/core/model/model.rb', line 101 def run_record_before_destroy_callbacks(record) record_before_destroy_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_before_save_callbacks(record) ⇒ Object
77 78 79 |
# File 'lib/yodel/models/core/model/model.rb', line 77 def run_record_before_save_callbacks(record) record_before_save_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_before_update_callbacks(record) ⇒ Object
93 94 95 |
# File 'lib/yodel/models/core/model/model.rb', line 93 def run_record_before_update_callbacks(record) record_before_update_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#run_record_before_validation_callbacks(record) ⇒ Object
Callbacks
TODO: use loops like in abstract record to write these functions
69 70 71 |
# File 'lib/yodel/models/core/model/model.rb', line 69 def run_record_before_validation_callbacks(record) record_before_validation_callbacks.each {|fn| Function.new(fn).execute(record)} end |
#to_str ⇒ Object
60 61 62 |
# File 'lib/yodel/models/core/model/model.rb', line 60 def to_str "#<Model: #{name}>" end |
#user_allowed_to?(user, action, record) ⇒ Boolean
Permissions
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/yodel/models/core/model/model.rb', line 224 def user_allowed_to?(user, action, record) case action when :view group = view_group when :update group = update_group when :delete group = delete_group when :create group = create_group end return true if group.nil? group.permitted?(user, record) end |
#user_allowed_to_create?(user, record) ⇒ Boolean
252 253 254 |
# File 'lib/yodel/models/core/model/model.rb', line 252 def user_allowed_to_create?(user, record) user_allowed_to?(user, :create, record) end |
#user_allowed_to_delete?(user, record) ⇒ Boolean
248 249 250 |
# File 'lib/yodel/models/core/model/model.rb', line 248 def user_allowed_to_delete?(user, record) user_allowed_to?(user, :delete, record) end |
#user_allowed_to_update?(user, record) ⇒ Boolean
244 245 246 |
# File 'lib/yodel/models/core/model/model.rb', line 244 def user_allowed_to_update?(user, record) user_allowed_to?(user, :update, record) end |
#user_allowed_to_view?(user, record) ⇒ Boolean
240 241 242 |
# File 'lib/yodel/models/core/model/model.rb', line 240 def user_allowed_to_view?(user, record) user_allowed_to?(user, :view, record) end |
#valid_child?(other_model) ⇒ Boolean
216 217 218 |
# File 'lib/yodel/models/core/model/model.rb', line 216 def valid_child?(other_model) valid_children.include?(other_model) end |
#valid_children ⇒ Object
Returns an array of all allowed children and descendants of those children. This list respects both allowed_children and allowed_parents restrictions, so Page (which allows children that are descendants of Page) won’t include Article which can only exist under a Blog page, even though Article is a descendant of Page.
212 213 214 |
# File 'lib/yodel/models/core/model/model.rb', line 212 def valid_children allowed_children_and_descendants.select {|child| child.allowed_parent?(self)} end |