Module: Stone::Resource

Defined in:
lib/stone/resource.rb

Overview

Adds the ability to persist any class it is included in

Example

class Post

include Stone::Resource

field :body, String

end

Constant Summary collapse

@@fields =

self

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#idObject

Returns the value of attribute id.



13
14
15
# File 'lib/stone/resource.rb', line 13

def id
  @id
end

Class Method Details

.included(base) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/stone/resource.rb', line 16

def included(base)
  rsrc_sym = base.to_s.make_key

  @@callbacks ||= Callbacks.new
  @@callbacks.register_klass(base)

  @@store ||= DataStore.new

  base.send(:extend, self)
  base.send(:include, ::Validatable)

  # rspec breaks if its classes have their contructor overwritten
  unless base.to_s.downcase =~ /(spec::example::examplegroup::subclass_\d|blah)/
    # allow object to be created with a hash of attributes...
    # [] allows for obj[attribute] retrieval
    # to_s allows for stupid Rails to work
    base.class_eval <<-EOS, __FILE__, __LINE__
      def initialize(hash = {})
        self.id = self.next_id_for_klass(self.class)
        unless hash.blank?
          update_attributes(hash)
        end
        @@fields[self.model].each do |f|
          instance_variable_set("@"+f[:name].to_s,[]) if f[:klass] == Array
        end
      end

      def to_s
        id
      end

      def [](sym)
        self.send(sym)
      end
    EOS
  end

  unless @@store.resources.include?(rsrc_sym)
    @@store.resources[rsrc_sym] = DataStore.load_data(rsrc_sym)
  end
end

Instance Method Details

#[](id) ⇒ Object

Synonymous for get

Parameters

id

id of the object to retrieve



249
250
251
252
253
# File 'lib/stone/resource.rb', line 249

def [](id)
  raise "Expected Fixnum, got #{id.class} for #{self.to_s}[]" \
    unless id.class == Fixnum || id.to_i
  get(id)
end

#after_create(meth) ⇒ Object

See before_save



138
139
140
# File 'lib/stone/resource.rb', line 138

def after_create(meth)
  @@callbacks.register(:after_create, meth, self)
end

#after_delete(meth) ⇒ Object

See before_save



148
149
150
# File 'lib/stone/resource.rb', line 148

def after_delete(meth)
  @@callbacks.register(:after_delete, meth, self)
end

#after_save(meth) ⇒ Object

See before_save



128
129
130
# File 'lib/stone/resource.rb', line 128

def after_save(meth)
  @@callbacks.register(:after_save, meth, self)
end

#all(conditions = nil) ⇒ Object

Returns all objects matching conditions, or all objects if no conditions are specified

Parameters

conditions

A hash representing one or more Ruby expressions



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/stone/resource.rb', line 213

def all(conditions = nil)
  objs = []
  # check for order conditions, make conditions nil if an order condition
  # is the only one passed to Resource.all
  if conditions && conditions[:order]
    order = conditions[:order].to_a.flatten
    conditions.delete(:order)
    conditions = nil if conditions.empty?
  end
  # Don't bother getting into crazy object searches if there are no
  # conditions -- just grab the objects directly out of the current 
  # store
  unless conditions
    @@store.resources[self.to_s.make_key].each do |o|
      objs << o[1]
    end
  else
    objs = find(conditions, self.to_s.make_key)
  end
  if order
    raise "Order should be passed with :asc or :desc, got #{order[1].inspect}" \
      unless [:asc,:desc].include? order[1]
    # FIXME: something about this breaks under certain conditions 
    # that I can't find yet
    begin
      objs.sort! {|x,y| x.send(order[0]) <=> y.send(order[0])}
    rescue
    end
    objs.reverse! if order[1] == :desc
  end
  objs
end

#already_exists?Boolean

Finds out if the object is already in the current DataStore instance

Returns:

  • (Boolean)


344
345
346
# File 'lib/stone/resource.rb', line 344

def already_exists?
  DataStore.determine_save_method(self, @@store) == :put
end

#before_create(meth) ⇒ Object

See before_save



133
134
135
# File 'lib/stone/resource.rb', line 133

def before_create(meth)
  @@callbacks.register(:before_create, meth, self)
end

#before_delete(meth) ⇒ Object

See before_save



143
144
145
# File 'lib/stone/resource.rb', line 143

def before_delete(meth) 
  @@callbacks.register(:before_delete, meth, self)
end

#before_save(meth) ⇒ Object

Registers the given method with the current instance of Callbacks. Upon activation (in this case, right before Resource.save is executed), the meth given is called against the object being, in this case, saved.

Note that there is no before_validation callback, because Stone before_save triggers before validations occur anyway

Parameters

meth

The method to be registered



123
124
125
# File 'lib/stone/resource.rb', line 123

def before_save(meth)
  @@callbacks.register(:before_save, meth, self)
end

#belongs_to(resource, *args) ⇒ Object

Registers a belongs_to association for resource

Parameters

resource

The resource to which this class belongs



175
176
177
178
179
180
181
182
# File 'lib/stone/resource.rb', line 175

def belongs_to(resource, *args)
  field "#{resource.to_s}_id".to_sym, Fixnum
  self.class_eval <<-EOS, __FILE__, __LINE__
    def #{resource.to_s} 
      #{resource.to_s.titlecase}[self.#{resource.to_s}_id]
    end
  EOS
end

#delete(id) ⇒ Object

Deletes the object with id from the current DataStore instance and its corresponding yaml file



261
262
263
264
265
266
267
268
269
# File 'lib/stone/resource.rb', line 261

def delete(id)
  fire(:before_delete)
  DataStore.delete(id, self.to_s.downcase.pluralize)
  @@store.resources[self.to_s.make_key].each_with_index do |o,i|
    @@store.resources[self.to_s.make_key].delete_at(i) if o[0] == id
  end
  fire(:after_delete)
  true
end

#field(name, klass, arg = nil) ⇒ Object

Adds a given field to @@fields and inserts an accessor for that field into klass

Parameters

name<String>


66
67
68
69
70
71
72
73
74
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
# File 'lib/stone/resource.rb', line 66

def field(name, klass, arg = nil)
  if arg && arg[:unique] == true
    unique = true
  else
    unique = false
  end
  klass_sym = self.to_s.make_key
  unless @@fields[klass_sym]
    @@fields[klass_sym] = [{:name => name, 
                            :klass => klass, 
                            :unique => unique}]
  else
    @@fields[klass_sym] << {:name => name, 
                            :klass => klass, 
                            :unique => unique}
  end
  name = name.to_s
  # add accessor for given field
  unless klass == Array
    self.class_eval <<-EOS, __FILE__, __LINE__
      def #{name}
        @#{name}
      end
      def #{name}=(value)
        @#{name} = value
      end
    EOS
  else
    self.class_eval <<-EOS, __FILE__, __LINE__
      def #{name}
        @#{name}
      end
    EOS
  end
  self.send(:define_method,"next_by_#{name}") {
    self.next(name.to_sym)
  }
  self.send(:define_method,"prev_by_#{name}") {
    self.prev(name.to_sym)
  }
end

#fieldsObject



255
256
257
# File 'lib/stone/resource.rb', line 255

def fields
  @@fields
end

#fields_are_valid?Boolean

Determines whether the field classes of a given object match the field class declarations

Returns:

  • (Boolean)


325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/stone/resource.rb', line 325

def fields_are_valid?
  klass_sym = self.model
  @@fields[klass_sym].each do |field|
    unless self.send(field[:name]).class == field[:klass] || self.send(field[:name]) == nil || self.already_exists?
      return false 
    end
    if field[:unique] == true
      return false if self.class.first(field[:name] => self.send(field[:name])) && !self.already_exists?
    end
  end
  true
end

#first(conditions = nil) ⇒ Object

Returns the first object matching conditions, or the first object if no conditions are specified

Parameters

conditions

A hash representing one or more Ruby expressions



200
201
202
203
204
205
206
# File 'lib/stone/resource.rb', line 200

def first(conditions = nil)
  unless conditions
    return @@store.resources[self.to_s.make_key].first[1]
  else
    return find(conditions, self.to_s.make_key)[0]
  end
end

#get(id) ⇒ Object Also known as: find_by_id

Allow for retrieval of an object in the current DataStore instance by id

Parameters

id

id of the object to retrieve



274
275
276
277
278
279
280
281
282
# File 'lib/stone/resource.rb', line 274

def get(id)
  id = id.to_i
  raise "Expected Fixnum, got #{id.class} for #{self.to_s}.get" \
    unless id.class == Fixnum
  @@store.resources[self.to_s.make_key].each do |o|
    return o[1] if o[0] == id
  end
  nil
end

#has_and_belongs_to_many(resource, *args) ⇒ Object Also known as: habtm

Registers a many-to-many association using an array of resource ids.

Parameters

resource

The resource of which this class belongs to and has many



189
190
191
# File 'lib/stone/resource.rb', line 189

def has_and_belongs_to_many(resource, *args)
  field resource, Array
end

#has_many(resource, *args) ⇒ Object

Registers a one-to-many relationship for resource

Parameters

resource

the resource of which this class has many



156
157
158
159
160
161
162
# File 'lib/stone/resource.rb', line 156

def has_many(resource, *args)
  self.class_eval <<-EOS, __FILE__, __LINE__
    def #{resource.to_s}
      #{resource.to_s.singularize.titlecase}.all("#{self.to_s.downcase}_id".to_sym.equals => self.id)
    end
  EOS
end

#has_one(resource, *args) ⇒ Object

Registers a one-to-one relationship for resource

Parameters

resource

the resource of which this class has one



168
169
170
# File 'lib/stone/resource.rb', line 168

def has_one(resource, *args)
  field "#{resource.to_s}_id".to_sym, Fixnum
end

#modelObject

field



108
109
110
# File 'lib/stone/resource.rb', line 108

def model
  self.class.to_s.downcase.to_sym
end

#modelsObject



111
112
113
# File 'lib/stone/resource.rb', line 111

def models
  self.class.to_s.downcase.pluralize
end

#new_record?Boolean

Needed for Rails to work

Returns:

  • (Boolean)


339
340
341
# File 'lib/stone/resource.rb', line 339

def new_record?
  !already_exists?
end

#next(order_by = :id) ⇒ Object



348
349
350
# File 'lib/stone/resource.rb', line 348

def next(order_by = :id)
  self.class.all(order_by.gt => self.send(order_by), :order => {order_by => :desc})[0]
end

#next_id_for_klass(klass) ⇒ Object

Determine the next id number to use based on the last stored object’s id of class klass

Parameters

klass

The class of the object to be saved



304
305
306
307
308
309
310
311
# File 'lib/stone/resource.rb', line 304

def next_id_for_klass(klass)
  sym = klass.to_s.make_key
  if @@store.resources.has_key?(sym) && !@@store.resources[sym].blank?
    return @@store.resources[sym].last[0] + 1
  else
    return 1
  end
end

#prev(order_by = :id) ⇒ Object



351
352
353
# File 'lib/stone/resource.rb', line 351

def prev(order_by = :id)
  self.class.all(order_by.lt => self.send(order_by), :order => {order_by => :desc})[0]
end

#saveObject

Save an object to the current DataStore instance.



314
315
316
317
318
319
320
321
# File 'lib/stone/resource.rb', line 314

def save
  return false unless self.fields_are_valid?
  fire(:before_save)
  return false unless self.valid?
  sym = DataStore.determine_save_method(self, @@store)
  self.class.send(sym, self)
  fire(:after_save)
end

#update_attributes(hash) ⇒ Object

Puts the attribute changes in hash

Parameters

hash

the attributes to change



288
289
290
291
292
293
294
295
296
297
298
# File 'lib/stone/resource.rb', line 288

def update_attributes(hash)
  hash.each_key do |k|
    k = k.to_sym
    if hash[k].is_a? Hash
      update_attributes(hash[k])
    else
      self.send(k.to_s+"=", hash[k]) if field_declared?(k,self.class)
    end
  end
  self.save
end