Module: PlainRecord::Associations

Included in:
Model
Defined in:
lib/plain_record/associations.rb

Overview

Extention for model to store or have link to another model. There is two type of association.

Virtual field

In virtual method this filter create only link to another model. When you try to use this virtual field, model will find association object by rules in map.

Rules in map is only Hash with model fields in key and association fields in value. For example, if model contain name field and association must have post_name with same value, map will be { :name => :post_name }.

If you didn’t set map filter will try to find it automatically: it will find in model and association class all field pairs, what have name like fieldmodel_field. For example, if model Post have field name and Comment have post_name, you may not set map – filter will find it automatically.

class Review
  include PlainRecord::Resource

  entry_in 'reviews/*.md'

  virtual :author, one(Author)
  field   :author_login
  text    :review
end

class Author
  include PlainRecord::Resource

  list_in 'authors.yml'

  virtual :reviews, many(Review)
  field   :login
  field   :name
end

Real field

If you will use this filter in field method, association object data will store in you model file. For example model:

class Movie
  include PlainRecord::Resource

  field :title
  field :genre
  field :release_year
end

class Tag
  include PlainRecord::Resource
  field :name
end

class Review
  include PlainRecord::Resource

  entry_in 'reviews/*.md'

  field :author
  field :movie, one(Movie)
  field :tags,  many(Tag)
  text  :review
end

will be store as:

author: John Smith
movie:
  title: Watchmen
  genre: action
  release_year: 2009
tags:
- name: Great movies
- name: Comics
---
Movie is great!

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#association_mapsObject

Hash with map for virtual associations.



103
104
105
# File 'lib/plain_record/associations.rb', line 103

def association_maps
  @association_maps
end

Class Method Details

Define that virtual field name in klass contain links to model witch are finded by map.



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/plain_record/associations.rb', line 252

def define_link_many(klass, model, name, map)
  klass.association_maps ||= { }
  klass.association_maps[name] = map
  init_association_cache(klass)

  klass.add_accessors <<-EOS, __FILE__, __LINE__
    def #{name}
      unless @association_cache.has_key? :#{name}
        search = Hash[
          self.class.association_maps[:#{name}].map do |from, to|
            [from, send(to)]
          end]
        @association_cache[:#{name}] = AssociationProxy.new(
            #{model}.all(search), self, :#{name})
      end
      @association_cache[:#{name}]
    end
    def #{name}=(values)
      @association_cache[:#{name}] = AssociationProxy.link(
          values, self, :#{name})
    end
  EOS
end

Define that virtual field name in klass contain link to model witch is finded by map.



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/plain_record/associations.rb', line 225

def define_link_one(klass, model, name, map)
  klass.association_maps ||= { }
  klass.association_maps[name] = map
  init_association_cache(klass)

  klass.add_accessors <<-EOS, __FILE__, __LINE__
    def #{name}
      unless @association_cache.has_key? :#{name}
        search = Hash[
          self.class.association_maps[:#{name}].map do |from, to|
            [to, send(from)]
          end]
        @association_cache[:#{name}] = #{model}.first(search)
      end
      @association_cache[:#{name}]
    end
    def #{name}=(value)
      self.class.association_maps[:#{name}].each do |from, to|
        value.send(to.to_s + '=', send(from))
      end
      @association_cache[:#{name}] = value
    end
  EOS
end

.define_real_many(klass, field, model) ⇒ Object

Define, that field in klass contain in file array of data from model objects.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/plain_record/associations.rb', line 169

def define_real_many(klass, field, model)
  name = field.to_s
  klass.after :load do |result, entry|
    if entry.data[name].is_a? Enumerable
      entry.data[name].map! { |i| model.new(entry.file, i) }
    else
      entry.data[name] = []
    end
    result
  end
  klass.before :save do |entry|
    if entry.data[name].empty?
      entry.data.delete(name)
    else
      entry.data[name].map! do |obj|
        model.call_before_callbacks(:save, [obj])
        obj.data
      end
    end
  end
  klass.after :save do |result, entry|
    entry.data[name].map! { |i| model.new(entry.file, i) }
    entry.data[name].each do |i|
      model.call_after_callbacks(:save, nil, [i])
    end
    result
  end
end

.define_real_one(klass, field, model) ⇒ Object

Define, that field in klass contain in file data from model.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/plain_record/associations.rb', line 144

def define_real_one(klass, field, model)
  name = field.to_s
  klass.after :load do |result, entry|
    if entry.data[name]
      entry.data[name] = model.new(entry.file, entry.data[name])
    end
    result
  end
  klass.before :save do |entry|
    if entry.data[name]
      model.call_before_callbacks(:save, [entry.data[name]])
      entry.data[name] = entry.data[name]
    end
  end
  klass.after :save do |result, entry|
    if entry.data[name]
      entry.data[name] = model.new(entry.file, entry.data[name])
      model.call_after_callbacks(:save, nil, [entry.data[name]])
    end
    result
  end
end

.init_association_cache(klass) ⇒ Object



217
218
219
220
221
# File 'lib/plain_record/associations.rb', line 217

def init_association_cache(klass)
  klass.after :load do |result, entry|
    entry.instance_exec { @association_cache = { } }
  end
end

.map(from, to, prefix) ⇒ Object

Find fields pairs in from and to models, witch is like prefix_fromto.

For example, if Comment contain post_name field and Post contain name:

Associations.map(Comment, Post, :post) #=> { :post_name => :name }


205
206
207
208
209
210
211
212
213
214
215
# File 'lib/plain_record/associations.rb', line 205

def map(from, to, prefix)
  from_fields = (from.fields + from.virtuals).map { |i| i.to_s }
  mapped = { }
  (to.fields + to.virtuals).each do |to_field|
    from_field = prefix + to_field.to_s
    if from_fields.include? from_field
      mapped[from_field.to_sym] = to_field
    end
  end
  mapped
end