Class: Record

Inherits:
SiteRecord show all
Defined in:
lib/yodel/models/core/record/record.rb

Direct Known Subclasses

APICall, Email, Group, Layout, Menu, Page, User

Constant Summary

Constants inherited from AbstractRecord

AbstractRecord::CALLBACKS, AbstractRecord::FIELD_CALLBACKS, AbstractRecord::ORDERS

Instance Attribute Summary collapse

Attributes inherited from SiteRecord

#site

Attributes inherited from AbstractRecord

#changed, #errors, #stash, #typecast, #values

Instance Method Summary collapse

Methods inherited from SiteRecord

#site_id

Methods included from SiteModel

#load, #scoped, #scoped_for

Methods included from MongoModel

#load, #scoped

Methods included from AbstractModel

#embed_many, #embed_one, #field, #many, #modify_field, #one, #remove_field

Methods inherited from MongoRecord

#id, #increment!, #load_from_mongo, #load_mongo_document, #perform_destroy, #perform_save, #set_id

Methods inherited from AbstractRecord

#changed!, #changed?, #clear_key, #destroy, #destroyed?, #eql?, #errors?, #field, #field?, #field_was, #from_json, #get, #get_meta, #get_raw, #hash, #id, #increment!, inherited, #inspect, #inspect_value, #method_missing, #new?, #present?, #reload, #save, #save_without_validation, #search_terms, #set, #set_meta, #set_raw, #to_json, #trigger_field_callback, #update, #valid?

Constructor Details

#initialize(model, site, values = {}, new_record = true) ⇒ Record

Returns a new instance of Record.



13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/yodel/models/core/record/record.rb', line 13

def initialize(model, site, values={}, new_record=true)
  @model_record = model
  @site = site
  @model  = load_model(model, values)
  @mixins = create_mixin_instances(values)
  super(site, values, new_record)

  # mixins have their db access methods delegated to the "real record"
  # (the main object representing the mongo document). To maintain a
  # transparency between objects, key instance variables in the mixin
  # are changed to refer to the same instance variables in the real record.
  delegate_mixins
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class AbstractRecord

Instance Attribute Details

#mixinsObject (readonly)

Returns the value of attribute mixins.



10
11
12
# File 'lib/yodel/models/core/record/record.rb', line 10

def mixins
  @mixins
end

#modelObject (readonly)

Returns the value of attribute model.



10
11
12
# File 'lib/yodel/models/core/record/record.rb', line 10

def model
  @model
end

#model_recordObject (readonly)

Returns the value of attribute model_record.



10
11
12
# File 'lib/yodel/models/core/record/record.rb', line 10

def model_record
  @model_record
end

#real_recordObject

reference to the ‘real’ record if this object is a mixin



11
12
13
# File 'lib/yodel/models/core/record/record.rb', line 11

def real_record
  @real_record
end

Instance Method Details

#all_childrenObject

All descendent children of this record, i.e children, grandchildren and so on.



295
296
297
# File 'lib/yodel/models/core/record/record.rb', line 295

def all_children
  [self, children.collect(&:all_children)].flatten
end

#append_to_siblingsObject



248
249
250
251
252
# File 'lib/yodel/models/core/record/record.rb', line 248

def append_to_siblings
  return unless new?
  highest_index = siblings.last.try(:index) || 0
  self.index = highest_index + 1
end

#children_and_selfObject

All direct descendents of this record, and the record itself



290
291
292
# File 'lib/yodel/models/core/record/record.rb', line 290

def children_and_self
  [self, children].flatten
end

#collectionObject



35
36
37
# File 'lib/yodel/models/core/record/record.rb', line 35

def collection
  Record.collection
end

#contentObject


Rendering




357
358
359
# File 'lib/yodel/models/core/record/record.rb', line 357

def content
  @content ||= get('content')
end

#create_eigenmodelObject



103
104
105
106
107
108
109
# File 'lib/yodel/models/core/record/record.rb', line 103

def create_eigenmodel
  return eigenmodel if eigenmodel?
  new_eigenmodel = model.create_model("#{id}_eigenmodel")
  self.eigenmodel = new_eigenmodel
  @model = new_eigenmodel
  save
end

#create_mixin_instances(values) ⇒ Object



125
126
127
128
129
130
# File 'lib/yodel/models/core/record/record.rb', line 125

def create_mixin_instances(values)
  return [] if @model.nil?
  @model.mixins.collect do |mixin_model|
    mixin_model.record_class.new(mixin_model, site, values)
  end.compact
end

#default_valuesObject



31
32
33
# File 'lib/yodel/models/core/record/record.rb', line 31

def default_values
  super.merge({'model' => model.id})
end

#delegate_mixinsObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/yodel/models/core/record/record.rb', line 132

def delegate_mixins
  extend SingleForwardable
  ancestors = self.class.ancestors
  included_classes = []

  mixins.each_with_index do |mixin, index|
    # reassign the mixin object's instance vars
    %w{@model @new @site @values @typecast @changed @errors @stash}.each do |var|
      mixin.instance_variable_set(var, instance_variable_get(var))
    end
  
    # delegate database access to the main object
    mixin.extend SingleForwardable
    mixin.real_record = self
    mixin.def_delegators :@real_record, :save, :save_without_validation, :destroy, :update,
                                        :reload, :fields
  
    # delegate mixin instance methods (if custom classes are used) to the mixin
    # so mixing in user to a page makes the page appear to have user methods
    # such as :reset_password. Delegation of methods continues up the class
    # hierarchy until the class ancestry of the main object and mixin converge
    # (only unique classes are mixed in). So mixing a user subclass into a page
    # would mixin the subclass, followed by user. We stop at record since
    # both page and the user subclass inherit from it.
    mixin.class.ancestors.each do |klass|
      break if ancestors.include?(klass)
      next if included_classes.include?(klass)
      def_delegators "@mixins[#{index}]", *klass.instance_methods(false)
      included_classes << klass
    end
  end
end

#destroy_childrenObject



274
275
276
# File 'lib/yodel/models/core/record/record.rb', line 274

def destroy_children
  children.each(&:destroy)
end

#field_sectionsObject



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/yodel/models/core/record/record.rb', line 80

def field_sections
  @sections ||= begin
    keyed_sections = Hash.new do |hash, key|
      hash[key] = Section.new(key)
    end
    fields.each do |name, field|
      keyed_sections[field.section] << field
    end
    keyed_sections
  end
end

#fieldsObject


Modelling




76
77
78
# File 'lib/yodel/models/core/record/record.rb', line 76

def fields
  @fields ||= @model.all_record_fields
end

#first_non_blank_response_to(message) ⇒ Object



342
343
344
345
346
347
348
349
350
351
# File 'lib/yodel/models/core/record/record.rb', line 342

def first_non_blank_response_to(message)
  message = message.to_s
  parents.find do |record|
    if record.respond_to?(message) || record.fields.keys.include?(message)
      value = record.send(message)
      return value unless value.blank?
    end
  end
  ''
end

#first_parent(type, exact = false) ⇒ Object

Returns the first parent which is an instance of type



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/yodel/models/core/record/record.rb', line 316

def first_parent(type, exact=false)
  model = site.model_by_plural_name(type.to_s.downcase.pluralize)
  
  if exact
    match = parents.find {|record| record.model == model}
  else
    match = parents.find {|record| record.model.parents_and_mixins.include?(model)}
  end

  if block_given?
    yield match
  else
    match
  end
end

#first_response_to(message) ⇒ Object

Finds the first parent which can respond to ‘message’ and returns the result. Will return nil if no parents response to the message, however, keep in mind that nil may be a valid response to this message.



335
336
337
338
339
340
# File 'lib/yodel/models/core/record/record.rb', line 335

def first_response_to(message)
  message = message.to_s
  parents.find do |record|
    return record.send(message) if record.respond_to?(message) || record.fields.keys.include?(message)
  end
end

#get_bindingObject



365
366
367
# File 'lib/yodel/models/core/record/record.rb', line 365

def get_binding
  binding
end

#has_eigenmodel?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/yodel/models/core/record/record.rb', line 117

def has_eigenmodel?
  self.eigenmodel != nil
end

#insert_in_siblings(new_index) ⇒ Object

FIXME: these need to be atomic ops over the whole set of children FIXME: it also seems weird to perform increments on siblings, but leave the index change to this record unchanged



256
257
258
259
260
261
262
263
264
# File 'lib/yodel/models/core/record/record.rb', line 256

def insert_in_siblings(new_index)
  original_parent = self.parent
  remove_from_siblings if index
  self.parent = original_parent
  siblings.where(:index.gte => new_index).each do |sibling|
    sibling.increment!(:index)
  end
  self.index = new_index
end

#inspect_hashObject



92
93
94
# File 'lib/yodel/models/core/record/record.rb', line 92

def inspect_hash
  {model: model, parent: parent, index: index}.merge(super)
end

#load_model(model, values) ⇒ Object



96
97
98
99
100
101
# File 'lib/yodel/models/core/record/record.rb', line 96

def load_model(model, values)
  return model if values['eigenmodel'].nil?
  eigenmodel = site.models.find(values['eigenmodel'])
  values['eigenmodel'] = nil if eigenmodel.nil?
  eigenmodel || model
end

#model_nameObject



121
122
123
# File 'lib/yodel/models/core/record/record.rb', line 121

def model_name
  has_eigenmodel? ? self.eigenmodel.parent.name : self.model_record.name
end

#parent?(record) ⇒ Boolean

True if record is a parent (ancestor) of this record

Returns:

  • (Boolean)


311
312
313
# File 'lib/yodel/models/core/record/record.rb', line 311

def parent?(record)
  parents.include?(record)
end

#parentsObject

An array of parent records all the way back to a root record. e.g calling on a page two levels deep would return: [page, parent, root]



301
302
303
# File 'lib/yodel/models/core/record/record.rb', line 301

def parents
  [self, self.parent.try(:parents)].flatten.compact
end

#perform_reload(params) ⇒ Object



39
40
41
42
# File 'lib/yodel/models/core/record/record.rb', line 39

def perform_reload(params)
  document = load_mongo_document(_id: params[:id])
  initialize(params[:model], params[:site], document)
end

#prepare_reload_paramsObject



44
45
46
# File 'lib/yodel/models/core/record/record.rb', line 44

def prepare_reload_params
  super.tap {|vals| vals[:model] = @model}
end

#remove_eigenmodelObject



111
112
113
114
115
# File 'lib/yodel/models/core/record/record.rb', line 111

def remove_eigenmodel
  eigenmodel.destroy if eigenmodel?
  self.eigenmodel = nil
  save
end

#remove_from_siblingsObject



266
267
268
269
270
271
272
# File 'lib/yodel/models/core/record/record.rb', line 266

def remove_from_siblings
  siblings.where(:index.gte => index).each do |sibling|
    sibling.increment!(:index, -1)
  end
  self.index = nil
  self.parent = nil
end

#root?Boolean

True if this record has no parent

Returns:

  • (Boolean)


306
307
308
# File 'lib/yodel/models/core/record/record.rb', line 306

def root?
  parent.nil?
end

#run_record_after_create_callbacksObject



214
215
216
# File 'lib/yodel/models/core/record/record.rb', line 214

def run_record_after_create_callbacks
  model.run_record_after_create_callbacks(self)
end

#run_record_after_destroy_callbacksObject



234
235
236
# File 'lib/yodel/models/core/record/record.rb', line 234

def run_record_after_destroy_callbacks
  model.run_record_after_destroy_callbacks(self)
end

#run_record_after_save_callbacksObject



204
205
206
# File 'lib/yodel/models/core/record/record.rb', line 204

def run_record_after_save_callbacks
  model.run_record_after_save_callbacks(self)
end

#run_record_after_update_callbacksObject



224
225
226
# File 'lib/yodel/models/core/record/record.rb', line 224

def run_record_after_update_callbacks
  model.run_record_after_update_callbacks(self)
end

#run_record_after_validation_callbacksObject



194
195
196
# File 'lib/yodel/models/core/record/record.rb', line 194

def run_record_after_validation_callbacks
  model.run_record_after_validation_callbacks(self)
end

#run_record_before_create_callbacksObject



209
210
211
# File 'lib/yodel/models/core/record/record.rb', line 209

def run_record_before_create_callbacks
  model.run_record_before_create_callbacks(self)
end

#run_record_before_destroy_callbacksObject



229
230
231
# File 'lib/yodel/models/core/record/record.rb', line 229

def run_record_before_destroy_callbacks
  model.run_record_before_destroy_callbacks(self)
end

#run_record_before_save_callbacksObject



199
200
201
# File 'lib/yodel/models/core/record/record.rb', line 199

def run_record_before_save_callbacks
  model.run_record_before_save_callbacks(self)
end

#run_record_before_update_callbacksObject



219
220
221
# File 'lib/yodel/models/core/record/record.rb', line 219

def run_record_before_update_callbacks
  model.run_record_before_update_callbacks(self)
end

#run_record_before_validation_callbacksObject



189
190
191
# File 'lib/yodel/models/core/record/record.rb', line 189

def run_record_before_validation_callbacks
  model.run_record_before_validation_callbacks(self)
end

#set_content(content) ⇒ Object



361
362
363
# File 'lib/yodel/models/core/record/record.rb', line 361

def set_content(content)
  @content = content
end

#siblingsObject

Siblings of this record (other records with the same parent)



279
280
281
282
283
284
285
286
287
# File 'lib/yodel/models/core/record/record.rb', line 279

def siblings
  unless parent.nil?
    model.unscoped.where(:parent => parent.try(:id), :_id.ne => id).order('index asc')
  else
    # A parent ID of nil indicates this record is the root of a tree. Since there
    # are multiple trees (including the model tree), a sibling query makes no sense.
    model.unscoped.where(:nonexistant_field => 'true')
  end
end

#to_strObject



27
28
29
# File 'lib/yodel/models/core/record/record.rb', line 27

def to_str
  "#<#{model_record.name}: #{id}>"
end

#update_search_keywordsObject



374
375
376
377
# File 'lib/yodel/models/core/record/record.rb', line 374

def update_search_keywords
  return unless model.searchable?
  self.search_keywords = search_terms
end

#user_allowed_to?(user, action) ⇒ Boolean


Permissions


Returns:

  • (Boolean)


52
53
54
# File 'lib/yodel/models/core/record/record.rb', line 52

def user_allowed_to?(user, action)
  model.user_allowed_to?(user, action, self)
end

#user_allowed_to_create?(user) ⇒ Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/yodel/models/core/record/record.rb', line 68

def user_allowed_to_create?(user)
  model.user_allowed_to?(user, :create, self)
end

#user_allowed_to_delete?(user) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/yodel/models/core/record/record.rb', line 64

def user_allowed_to_delete?(user)
  model.user_allowed_to?(user, :delete, self)
end

#user_allowed_to_update?(user) ⇒ Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/yodel/models/core/record/record.rb', line 60

def user_allowed_to_update?(user)
  model.user_allowed_to?(user, :update, self)
end

#user_allowed_to_view?(user) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/yodel/models/core/record/record.rb', line 56

def user_allowed_to_view?(user)
  model.user_allowed_to?(user, :view, self)
end