bomberstudios-stone 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/stone.rb +55 -0
- data/lib/stone/callbacks.rb +50 -0
- data/lib/stone/core_ext/datetime.rb +18 -0
- data/lib/stone/core_ext/enumerable.rb +5 -0
- data/lib/stone/core_ext/string.rb +12 -0
- data/lib/stone/core_ext/symbol.rb +25 -0
- data/lib/stone/data_store.rb +70 -0
- data/lib/stone/query.rb +44 -0
- data/lib/stone/resource.rb +472 -0
- data/lib/stone/version.rb +9 -0
- metadata +62 -0
data/lib/stone.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'validatable'
|
4
|
+
require 'english/inflect'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/core_ext/enumerable')
|
8
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/core_ext/string')
|
9
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/core_ext/symbol')
|
10
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/core_ext/datetime')
|
11
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/query')
|
12
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/data_store')
|
13
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/callbacks')
|
14
|
+
require File.expand_path(File.dirname(__FILE__) + '/stone/resource')
|
15
|
+
|
16
|
+
STONE_ROOT = Dir.pwd
|
17
|
+
|
18
|
+
module Stone
|
19
|
+
class << self
|
20
|
+
|
21
|
+
# For spec stuff only
|
22
|
+
def empty_datastore
|
23
|
+
if File.exists? STONE_ROOT/"sandbox_for_specs/datastore"
|
24
|
+
FileUtils.rm_rf STONE_ROOT/"sandbox_for_specs/datastore"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates or updates a datastore at +path+
|
29
|
+
# === Parameters
|
30
|
+
# +path+<String>::
|
31
|
+
# Path to create or update datastore (usually an application's root)
|
32
|
+
# +resources+<Array>:: A list of resources that exist for the application
|
33
|
+
def start(path, resources, framework = nil)
|
34
|
+
DataStore.local_dir = path/"datastore"
|
35
|
+
|
36
|
+
# create the datastore dir unless it exists
|
37
|
+
FileUtils.mkdir_p(DataStore.local_dir) unless File.exists?(DataStore.local_dir)
|
38
|
+
|
39
|
+
# create a .stone_metadata that contains the resource locations
|
40
|
+
# for Stone::Utilities to use
|
41
|
+
File.open(DataStore.local_dir/".stone_metadata", "w") do |out|
|
42
|
+
YAML.dump({:rsrc_path => File.dirname(resources.first)}, out)
|
43
|
+
end unless File.exists?(DataStore.local_dir/".stone_metadata")
|
44
|
+
|
45
|
+
# load each resource unless a framework has already done it
|
46
|
+
resources.each do |resource|
|
47
|
+
require resource unless framework == :merb || framework == :rails
|
48
|
+
name = File.basename(resource,".rb").pluralize
|
49
|
+
unless File.exists? DataStore.local_dir/name
|
50
|
+
FileUtils.mkdir_p(DataStore.local_dir/name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end # self
|
55
|
+
end # Stone
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Stone
|
2
|
+
class Callbacks < Hash
|
3
|
+
|
4
|
+
CALLBACKS = [
|
5
|
+
:before_save,
|
6
|
+
:after_save,
|
7
|
+
:before_create,
|
8
|
+
:after_create,
|
9
|
+
:before_destroy,
|
10
|
+
:after_destroy
|
11
|
+
]
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
end # self
|
16
|
+
|
17
|
+
# Registers the +klass+ with the current instance of Callbacks in Resource
|
18
|
+
# === Parameters
|
19
|
+
# +klass+:: The class to be registered
|
20
|
+
def register_klass(klass)
|
21
|
+
self[klass.to_s.make_key] = {}
|
22
|
+
CALLBACKS.each do |cb_sym|
|
23
|
+
self[klass.to_s.make_key][cb_sym] = []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Adds a given +meth+ to the +cb_sym+ array in the current Callbacks
|
28
|
+
# hash based on +klass+
|
29
|
+
# === Parameters
|
30
|
+
# +cb_sym+:: where +meth+ will be added
|
31
|
+
# +meth+:: method to be added
|
32
|
+
# +klass+:: determines which +cb_sym+ array will be used
|
33
|
+
def register(cb_sym, meth, klass)
|
34
|
+
self[klass.to_s.make_key][cb_sym] << meth
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sends any methods registered under +cb_sym+ to +obj+
|
38
|
+
# === Parameters
|
39
|
+
# +cb_sym+:: Used to retrieve the methods to send
|
40
|
+
# +obj+:: The object to which the retrieved methods are sent
|
41
|
+
def fire(cb_sym, obj)
|
42
|
+
unless obj.model == :class
|
43
|
+
self[obj.model][cb_sym].each do |meth|
|
44
|
+
obj.send(meth) unless meth.blank?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end # Callbacks
|
50
|
+
end # Stone
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Fixes the bug with YAML and DateTime, where
|
2
|
+
# YAML::load(DateTime.now.to_yaml).class == Time
|
3
|
+
require "yaml"
|
4
|
+
class DateTime
|
5
|
+
yaml_as "tag:ruby.yaml.org,2002:datetime"
|
6
|
+
def DateTime.yaml_new(klass, tag, val)
|
7
|
+
if String === val
|
8
|
+
self.parse(val)
|
9
|
+
else
|
10
|
+
raise YAML::TypeError, "Invalid DateTime: " + val.inspect
|
11
|
+
end
|
12
|
+
end
|
13
|
+
def to_yaml( opts = {} )
|
14
|
+
YAML::quick_emit( object_id, opts ) do |out|
|
15
|
+
out.scalar( taguri, self.to_s, :plain )
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Symbol
|
2
|
+
def gt; Stone::Query.new(self.to_s, :gt); end
|
3
|
+
def gte; Stone::Query.new(self.to_s, :gte); end
|
4
|
+
def lt; Stone::Query.new(self.to_s, :lt); end
|
5
|
+
def lte; Stone::Query.new(self.to_s, :lte); end
|
6
|
+
|
7
|
+
def includes
|
8
|
+
Stone::Query.new(self.to_s, :includes)
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches
|
12
|
+
Stone::Query.new(self.to_s, :matches)
|
13
|
+
end
|
14
|
+
|
15
|
+
def equals
|
16
|
+
Stone::Query.new(self.to_s, :equals)
|
17
|
+
end
|
18
|
+
|
19
|
+
def not
|
20
|
+
Stone::Query.new(self.to_s, :not)
|
21
|
+
end
|
22
|
+
def blank?
|
23
|
+
return self.to_s.size <= 0
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Stone
|
2
|
+
# An in-memory representation of the file-based datastore
|
3
|
+
class DataStore
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :local_dir
|
7
|
+
|
8
|
+
# Loads yaml files specific to the resource represented by +sym+
|
9
|
+
# === Parameters
|
10
|
+
# +sym+::
|
11
|
+
# Symbol representing resource data to load
|
12
|
+
def load_data(sym)
|
13
|
+
ymls = Dir.glob(self.local_dir/sym.to_s.pluralize/"*.yml").sort { |a,b| File.basename(a,".yml").to_i - File.basename(b,".yml").to_i }
|
14
|
+
objs = []
|
15
|
+
unless ymls.empty?
|
16
|
+
ymls.each do |yml|
|
17
|
+
obj = YAML.load_file yml
|
18
|
+
objs << [obj.id, obj]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
return objs
|
22
|
+
end
|
23
|
+
|
24
|
+
# If the object already exists (id was found),
|
25
|
+
# this returns +put+(update), else +post+(create)
|
26
|
+
# === Parameters
|
27
|
+
# +obj+::
|
28
|
+
# The instantiated resource to be saved
|
29
|
+
# +store+::
|
30
|
+
# DataStore object
|
31
|
+
def determine_save_method(obj, store)
|
32
|
+
store.resources[obj.model].each do |o|
|
33
|
+
return :put if o[0] == obj.id
|
34
|
+
end
|
35
|
+
:post
|
36
|
+
end
|
37
|
+
|
38
|
+
# Persist the object via YAML
|
39
|
+
# === Parameters
|
40
|
+
# +obj+:: The object to be persisted
|
41
|
+
def write_yaml(obj)
|
42
|
+
path = self.local_dir/obj.models/"#{obj.id}.yml"
|
43
|
+
File.open(path, 'w') do |out|
|
44
|
+
YAML.dump(obj, out)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Removes object's yaml file
|
49
|
+
# === Parameters
|
50
|
+
# +id+:: id of the object to be removed
|
51
|
+
# +klass_dir+:: directory in which object resides
|
52
|
+
def delete(id, klass_dir)
|
53
|
+
raise "Object could not be found" \
|
54
|
+
unless File.exists?(self.local_dir/klass_dir/"#{id}.yml")
|
55
|
+
|
56
|
+
FileUtils.remove_file(self.local_dir/klass_dir/"#{id}.yml")
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end # self
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
@resources = {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def resources
|
66
|
+
@resources
|
67
|
+
end
|
68
|
+
|
69
|
+
end # DataStore
|
70
|
+
end # Stone
|
data/lib/stone/query.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Stone
|
2
|
+
# Represents a single query condition
|
3
|
+
class Query
|
4
|
+
|
5
|
+
attr_accessor :field, :op
|
6
|
+
|
7
|
+
def initialize(field,op)
|
8
|
+
self.field = field
|
9
|
+
self.op = case op
|
10
|
+
when :gt
|
11
|
+
".>"
|
12
|
+
when :lt
|
13
|
+
".<"
|
14
|
+
when :gte
|
15
|
+
".>="
|
16
|
+
when :lte
|
17
|
+
".<="
|
18
|
+
when :includes
|
19
|
+
".include?"
|
20
|
+
when :matches
|
21
|
+
".=~"
|
22
|
+
when :equals
|
23
|
+
".=="
|
24
|
+
when :not
|
25
|
+
".!="
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Builds an expression from the conditions given during first or all
|
30
|
+
# === Parameters
|
31
|
+
# +arg+:: Some conditional argument
|
32
|
+
def expression_for(arg)
|
33
|
+
if arg.is_a? String
|
34
|
+
"#{self.field}#{self.op}('#{arg}')"
|
35
|
+
elsif arg.is_a? Regexp
|
36
|
+
"#{self.field}#{self.op}(#{arg.inspect})"
|
37
|
+
elsif arg.is_a? Date
|
38
|
+
"#{self.field}#{self.op}(DateTime.parse('#{arg}'))"
|
39
|
+
else
|
40
|
+
"#{self.field}#{self.op}(#{arg})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,472 @@
|
|
1
|
+
module Stone
|
2
|
+
# Adds the ability to persist any class it is included in
|
3
|
+
# === Example
|
4
|
+
#
|
5
|
+
# class Post
|
6
|
+
# include Stone::Resource
|
7
|
+
#
|
8
|
+
# field :body, String
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
module Resource
|
12
|
+
|
13
|
+
attr_accessor :id
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def included(base)
|
17
|
+
rsrc_sym = base.to_s.make_key
|
18
|
+
|
19
|
+
@@callbacks ||= Callbacks.new
|
20
|
+
@@callbacks.register_klass(base)
|
21
|
+
|
22
|
+
@@store ||= DataStore.new
|
23
|
+
|
24
|
+
base.send(:extend, self)
|
25
|
+
base.send(:include, ::Validatable)
|
26
|
+
|
27
|
+
# rspec breaks if its classes have their contructor overwritten
|
28
|
+
unless base.to_s.downcase =~ /(spec::example::examplegroup::subclass_\d|blah)/
|
29
|
+
# allow object to be created with a hash of attributes...
|
30
|
+
# [] allows for obj[attribute] retrieval
|
31
|
+
# to_s allows for stupid Rails to work
|
32
|
+
base.class_eval <<-EOS, __FILE__, __LINE__
|
33
|
+
def initialize(hash = {})
|
34
|
+
self.id = self.next_id_for_klass(self.class)
|
35
|
+
unless hash.blank?
|
36
|
+
update_attributes(hash)
|
37
|
+
end
|
38
|
+
@@fields[self.model].each do |f|
|
39
|
+
instance_variable_set("@"+f[:name].to_s,[]) if f[:klass] == Array
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
id
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](sym)
|
48
|
+
self.send(sym)
|
49
|
+
end
|
50
|
+
EOS
|
51
|
+
end
|
52
|
+
|
53
|
+
unless @@store.resources.include?(rsrc_sym)
|
54
|
+
@@store.resources[rsrc_sym] = DataStore.load_data(rsrc_sym)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end # self
|
58
|
+
|
59
|
+
@@fields = {}
|
60
|
+
|
61
|
+
# Adds a given field to @@fields and inserts an accessor for that
|
62
|
+
# field into klass
|
63
|
+
# === Parameters
|
64
|
+
# +name+<String>::
|
65
|
+
#
|
66
|
+
def field(name, klass, arg = nil)
|
67
|
+
if arg && arg[:unique] == true
|
68
|
+
unique = true
|
69
|
+
else
|
70
|
+
unique = false
|
71
|
+
end
|
72
|
+
klass_sym = self.to_s.make_key
|
73
|
+
unless @@fields[klass_sym]
|
74
|
+
@@fields[klass_sym] = [{:name => name,
|
75
|
+
:klass => klass,
|
76
|
+
:unique => unique}]
|
77
|
+
else
|
78
|
+
@@fields[klass_sym] << {:name => name,
|
79
|
+
:klass => klass,
|
80
|
+
:unique => unique}
|
81
|
+
end
|
82
|
+
name = name.to_s
|
83
|
+
# add accessor for given field
|
84
|
+
unless klass == Array
|
85
|
+
self.class_eval <<-EOS, __FILE__, __LINE__
|
86
|
+
def #{name}
|
87
|
+
@#{name}
|
88
|
+
end
|
89
|
+
def #{name}=(value)
|
90
|
+
@#{name} = value
|
91
|
+
end
|
92
|
+
EOS
|
93
|
+
else
|
94
|
+
self.class_eval <<-EOS, __FILE__, __LINE__
|
95
|
+
def #{name}
|
96
|
+
@#{name}
|
97
|
+
end
|
98
|
+
EOS
|
99
|
+
end
|
100
|
+
self.send(:define_method,"next_by_#{name}") {
|
101
|
+
self.next(name.to_sym)
|
102
|
+
}
|
103
|
+
self.send(:define_method,"prev_by_#{name}") {
|
104
|
+
self.prev(name.to_sym)
|
105
|
+
}
|
106
|
+
end # field
|
107
|
+
|
108
|
+
def model
|
109
|
+
self.class.to_s.downcase.to_sym
|
110
|
+
end
|
111
|
+
def models
|
112
|
+
self.class.to_s.downcase.pluralize
|
113
|
+
end
|
114
|
+
|
115
|
+
# Registers the given method with the current instance of Callbacks. Upon
|
116
|
+
# activation (in this case, right before Resource.save is executed), the
|
117
|
+
# +meth+ given is called against the object being, in this case, saved.
|
118
|
+
#
|
119
|
+
# Note that there is no before_validation callback, because Stone
|
120
|
+
# before_save triggers before validations occur anyway
|
121
|
+
# === Parameters
|
122
|
+
# +meth+:: The method to be registered
|
123
|
+
def before_save(meth)
|
124
|
+
@@callbacks.register(:before_save, meth, self)
|
125
|
+
end
|
126
|
+
|
127
|
+
# See before_save
|
128
|
+
def after_save(meth)
|
129
|
+
@@callbacks.register(:after_save, meth, self)
|
130
|
+
end
|
131
|
+
|
132
|
+
# See before_save
|
133
|
+
def before_create(meth)
|
134
|
+
@@callbacks.register(:before_create, meth, self)
|
135
|
+
end
|
136
|
+
|
137
|
+
# See before_save
|
138
|
+
def after_create(meth)
|
139
|
+
@@callbacks.register(:after_create, meth, self)
|
140
|
+
end
|
141
|
+
|
142
|
+
# See before_save
|
143
|
+
def before_delete(meth)
|
144
|
+
@@callbacks.register(:before_delete, meth, self)
|
145
|
+
end
|
146
|
+
|
147
|
+
# See before_save
|
148
|
+
def after_delete(meth)
|
149
|
+
@@callbacks.register(:after_delete, meth, self)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Registers a one-to-many relationship for +resource+
|
153
|
+
# === Parameters
|
154
|
+
# +resource+::
|
155
|
+
# the resource of which this class has many
|
156
|
+
def has_many(resource, *args)
|
157
|
+
self.class_eval <<-EOS, __FILE__, __LINE__
|
158
|
+
def #{resource.to_s}
|
159
|
+
#{resource.to_s.singularize.titlecase}.all("#{self.to_s.downcase}_id".to_sym.equals => self.id)
|
160
|
+
end
|
161
|
+
EOS
|
162
|
+
end
|
163
|
+
|
164
|
+
# Registers a one-to-one relationship for +resource+
|
165
|
+
# === Parameters
|
166
|
+
# +resource+::
|
167
|
+
# the resource of which this class has one
|
168
|
+
def has_one(resource, *args)
|
169
|
+
field "#{resource.to_s}_id".to_sym, Fixnum
|
170
|
+
end
|
171
|
+
|
172
|
+
# Registers a belongs_to association for +resource+
|
173
|
+
# === Parameters
|
174
|
+
# +resource+ :: The resource to which this class belongs
|
175
|
+
def belongs_to(resource, *args)
|
176
|
+
field "#{resource.to_s}_id".to_sym, Fixnum
|
177
|
+
self.class_eval <<-EOS, __FILE__, __LINE__
|
178
|
+
def #{resource.to_s}
|
179
|
+
#{resource.to_s.titlecase}[self.#{resource.to_s}_id]
|
180
|
+
end
|
181
|
+
EOS
|
182
|
+
end
|
183
|
+
|
184
|
+
# Registers a many-to-many association using an array of +resource+
|
185
|
+
# ids.
|
186
|
+
# === Parameters
|
187
|
+
# +resource+::
|
188
|
+
# The resource of which this class belongs to and has many
|
189
|
+
def has_and_belongs_to_many(resource, *args)
|
190
|
+
field resource, Array
|
191
|
+
end
|
192
|
+
|
193
|
+
alias_method :habtm, :has_and_belongs_to_many
|
194
|
+
|
195
|
+
# Returns the first object matching +conditions+, or the first object
|
196
|
+
# if no conditions are specified
|
197
|
+
# === Parameters
|
198
|
+
# +conditions+::
|
199
|
+
# A hash representing one or more Ruby expressions
|
200
|
+
def first(conditions = nil)
|
201
|
+
unless conditions
|
202
|
+
return @@store.resources[self.to_s.make_key].first[1]
|
203
|
+
else
|
204
|
+
return find(conditions, self.to_s.make_key)[0]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns all objects matching +conditions+, or all objects if no
|
209
|
+
# conditions are specified
|
210
|
+
# === Parameters
|
211
|
+
# +conditions+::
|
212
|
+
# A hash representing one or more Ruby expressions
|
213
|
+
def all(conditions = nil)
|
214
|
+
objs = []
|
215
|
+
# check for order conditions, make conditions nil if an order condition
|
216
|
+
# is the only one passed to Resource.all
|
217
|
+
if conditions && conditions[:order]
|
218
|
+
order = conditions[:order].to_a.flatten
|
219
|
+
conditions.delete(:order)
|
220
|
+
conditions = nil if conditions.empty?
|
221
|
+
end
|
222
|
+
# Don't bother getting into crazy object searches if there are no
|
223
|
+
# conditions -- just grab the objects directly out of the current
|
224
|
+
# store
|
225
|
+
unless conditions
|
226
|
+
@@store.resources[self.to_s.make_key].each do |o|
|
227
|
+
objs << o[1]
|
228
|
+
end
|
229
|
+
else
|
230
|
+
objs = find(conditions, self.to_s.make_key)
|
231
|
+
end
|
232
|
+
if order
|
233
|
+
raise "Order should be passed with :asc or :desc, got #{order[1].inspect}" \
|
234
|
+
unless [:asc,:desc].include? order[1]
|
235
|
+
# FIXME: something about this breaks under certain conditions
|
236
|
+
# that I can't find yet
|
237
|
+
begin
|
238
|
+
objs.sort! {|x,y| x.send(order[0]) <=> y.send(order[0])}
|
239
|
+
rescue
|
240
|
+
end
|
241
|
+
objs.reverse! if order[1] == :desc
|
242
|
+
end
|
243
|
+
objs
|
244
|
+
end
|
245
|
+
|
246
|
+
# Synonymous for get
|
247
|
+
# === Parameters
|
248
|
+
# +id+:: id of the object to retrieve
|
249
|
+
def [](id)
|
250
|
+
raise "Expected Fixnum, got #{id.class} for #{self.to_s}[]" \
|
251
|
+
unless id.class == Fixnum || id.to_i
|
252
|
+
get(id)
|
253
|
+
end
|
254
|
+
|
255
|
+
def fields
|
256
|
+
@@fields
|
257
|
+
end
|
258
|
+
|
259
|
+
# Deletes the object with +id+ from the current DataStore instance and
|
260
|
+
# its corresponding yaml file
|
261
|
+
def delete(id)
|
262
|
+
fire(:before_delete)
|
263
|
+
DataStore.delete(id, self.to_s.downcase.pluralize)
|
264
|
+
@@store.resources[self.to_s.make_key].each_with_index do |o,i|
|
265
|
+
@@store.resources[self.to_s.make_key].delete_at(i) if o[0] == id
|
266
|
+
end
|
267
|
+
fire(:after_delete)
|
268
|
+
true
|
269
|
+
end
|
270
|
+
|
271
|
+
# Allow for retrieval of an object in the current DataStore instance by id
|
272
|
+
# === Parameters
|
273
|
+
# +id+:: id of the object to retrieve
|
274
|
+
def get(id)
|
275
|
+
id = id.to_i
|
276
|
+
raise "Expected Fixnum, got #{id.class} for #{self.to_s}.get" \
|
277
|
+
unless id.class == Fixnum
|
278
|
+
@@store.resources[self.to_s.make_key].each do |o|
|
279
|
+
return o[1] if o[0] == id
|
280
|
+
end
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
alias_method :find_by_id, :get
|
284
|
+
|
285
|
+
# Puts the attribute changes in +hash+
|
286
|
+
# === Parameters
|
287
|
+
# +hash+:: the attributes to change
|
288
|
+
def update_attributes(hash)
|
289
|
+
hash.each_key do |k|
|
290
|
+
k = k.to_sym
|
291
|
+
if hash[k].is_a? Hash
|
292
|
+
update_attributes(hash[k])
|
293
|
+
else
|
294
|
+
self.send(k.to_s+"=", hash[k]) if field_declared?(k,self.class)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
self.save
|
298
|
+
end
|
299
|
+
|
300
|
+
# Determine the next id number to use based on the last stored object's id
|
301
|
+
# of class +klass+
|
302
|
+
# === Parameters
|
303
|
+
# +klass+:: The class of the object to be saved
|
304
|
+
def next_id_for_klass(klass)
|
305
|
+
sym = klass.to_s.make_key
|
306
|
+
if @@store.resources.has_key?(sym) && !@@store.resources[sym].blank?
|
307
|
+
return @@store.resources[sym].last[0] + 1
|
308
|
+
else
|
309
|
+
return 1
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Save an object to the current DataStore instance.
|
314
|
+
def save
|
315
|
+
return false unless self.fields_are_valid?
|
316
|
+
fire(:before_save)
|
317
|
+
return false unless self.valid?
|
318
|
+
sym = DataStore.determine_save_method(self, @@store)
|
319
|
+
self.class.send(sym, self)
|
320
|
+
fire(:after_save)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Determines whether the field classes of a given object match the field
|
324
|
+
# class declarations
|
325
|
+
def fields_are_valid?
|
326
|
+
klass_sym = self.model
|
327
|
+
@@fields[klass_sym].each do |field|
|
328
|
+
unless self.send(field[:name]).class == field[:klass] || self.send(field[:name]) == nil || self.already_exists?
|
329
|
+
return false
|
330
|
+
end
|
331
|
+
if field[:unique] == true
|
332
|
+
return false if self.class.first(field[:name] => self.send(field[:name])) && !self.already_exists?
|
333
|
+
end
|
334
|
+
end
|
335
|
+
true
|
336
|
+
end
|
337
|
+
|
338
|
+
# Needed for Rails to work
|
339
|
+
def new_record?
|
340
|
+
!already_exists?
|
341
|
+
end
|
342
|
+
|
343
|
+
# Finds out if the object is already in the current DataStore instance
|
344
|
+
def already_exists?
|
345
|
+
DataStore.determine_save_method(self, @@store) == :put
|
346
|
+
end
|
347
|
+
|
348
|
+
def next(order_by = :id)
|
349
|
+
self.class.all(order_by.gt => self.send(order_by), :order => {order_by => :desc})[0]
|
350
|
+
end
|
351
|
+
def prev(order_by = :id)
|
352
|
+
self.class.all(order_by.lt => self.send(order_by), :order => {order_by => :desc})[0]
|
353
|
+
end
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
# Fires the given callback in the current instance of Callbacks
|
358
|
+
# === Parameters
|
359
|
+
# +cb_sym+:: The symbol for the callback (e.g. :before_save)
|
360
|
+
def fire(cb_sym)
|
361
|
+
@@callbacks.fire(cb_sym, self)
|
362
|
+
true
|
363
|
+
end
|
364
|
+
|
365
|
+
# Creates a yaml file for +obj+ and adds +obj+ to the current DataStore
|
366
|
+
# instance
|
367
|
+
# === Parameters
|
368
|
+
# +obj+:: The object to be saved
|
369
|
+
def post(obj)
|
370
|
+
fire(:before_create)
|
371
|
+
obj.created_at = DateTime.now if field_declared?(:created_at,obj.class)
|
372
|
+
obj.updated_at = DateTime.now if field_declared?(:updated_at,obj.class)
|
373
|
+
DataStore.write_yaml(obj)
|
374
|
+
@@store.resources[obj.model] << [obj.id, obj]
|
375
|
+
fire(:after_create)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Updates the yaml file for +obj+ and overwrites the old object in the
|
379
|
+
# the current DataStore instance
|
380
|
+
# === Parameters
|
381
|
+
#
|
382
|
+
def put(obj)
|
383
|
+
obj.updated_at = DateTime.now if field_declared?(:updated_at,obj.class)
|
384
|
+
DataStore.write_yaml(obj)
|
385
|
+
@@store.resources[obj.model].each do |o|
|
386
|
+
o[1] = obj if o[0] == obj.id
|
387
|
+
end
|
388
|
+
true
|
389
|
+
end
|
390
|
+
|
391
|
+
# Find an object according to +conditions+ provided
|
392
|
+
# === Parameters
|
393
|
+
# +conditions+:: A plain string representation of a set of conditions
|
394
|
+
# +key+::
|
395
|
+
# A symbol representing the class of objects to look for in the current
|
396
|
+
# DataStore instance
|
397
|
+
def find(conditions, key) #:doc:
|
398
|
+
objs = []
|
399
|
+
|
400
|
+
if conditions.is_a? Hash
|
401
|
+
unless conditions.to_a.flatten.map {|e| e.is_a? Query}.include?(true)
|
402
|
+
conds = conditions.to_a.flatten
|
403
|
+
@@store.resources[key].each do |o|
|
404
|
+
objs << o[1] if o[1].send(conds[0]) == conds[1]
|
405
|
+
end
|
406
|
+
else
|
407
|
+
parsed_conditions = parse_conditions(conditions)
|
408
|
+
@@store.resources[key].each do |o|
|
409
|
+
objs << o[1] if matches_conditions?(o[1], parsed_conditions)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
else
|
413
|
+
raise "Resource.find expects a Hash, got a #{conditions.class}"
|
414
|
+
end
|
415
|
+
objs
|
416
|
+
end
|
417
|
+
|
418
|
+
# Checks the list of fields for a given +klass+ to see if +field+
|
419
|
+
# is included
|
420
|
+
# === Parameters
|
421
|
+
# +field+:: The field to look for
|
422
|
+
# +klass+:: The class to look in
|
423
|
+
def field_declared?(field,klass)
|
424
|
+
@@fields[klass.to_s.make_key].each do |f|
|
425
|
+
return true if f[:name] == field
|
426
|
+
end
|
427
|
+
false
|
428
|
+
end
|
429
|
+
|
430
|
+
# Executes and evaluates the expressions in +conds+ against
|
431
|
+
# the +obj+ provided, and then evaluates those results against
|
432
|
+
# the conditionals ("&&") in +conds+
|
433
|
+
# === Parameters
|
434
|
+
# +obj+:: The object to compare against
|
435
|
+
# +conds+::
|
436
|
+
# A set of expressions (name == 'nick') and their conditionals
|
437
|
+
# ('&&')
|
438
|
+
def matches_conditions?(obj, conds) #:doc:
|
439
|
+
tf_ary = []
|
440
|
+
conds.each_with_index do |cond,i|
|
441
|
+
# build an array like [true, "&&", false, "&&", true]
|
442
|
+
if i % 2 == 0
|
443
|
+
begin
|
444
|
+
bool = obj.instance_eval(cond)
|
445
|
+
bool = false unless bool
|
446
|
+
bool = true if bool.class == Fixnum
|
447
|
+
tf_ary << bool
|
448
|
+
rescue
|
449
|
+
tf_ary << false
|
450
|
+
end
|
451
|
+
else
|
452
|
+
tf_ary << cond
|
453
|
+
end
|
454
|
+
end
|
455
|
+
# evaluate the true/false array
|
456
|
+
eval(tf_ary.join)
|
457
|
+
end
|
458
|
+
|
459
|
+
# Turns conditions into a set of expressions that can be evaluated
|
460
|
+
def parse_conditions(hash)
|
461
|
+
conds = []
|
462
|
+
hash.each do |k,v|
|
463
|
+
conds << k.expression_for(v)
|
464
|
+
conds << "&&"
|
465
|
+
end
|
466
|
+
conds.pop
|
467
|
+
conds
|
468
|
+
end
|
469
|
+
|
470
|
+
end # Resource
|
471
|
+
|
472
|
+
end # Stone
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bomberstudios-stone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.2"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Ale Mu\xC3\xB1oz"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-11 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Super-simple data persistence layer created for small applications.
|
17
|
+
email: bomberstudios@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/stone.rb
|
26
|
+
- lib/stone/callbacks.rb
|
27
|
+
- lib/stone/data_store.rb
|
28
|
+
- lib/stone/query.rb
|
29
|
+
- lib/stone/resource.rb
|
30
|
+
- lib/stone/version.rb
|
31
|
+
- lib/stone/core_ext/datetime.rb
|
32
|
+
- lib/stone/core_ext/enumerable.rb
|
33
|
+
- lib/stone/core_ext/string.rb
|
34
|
+
- lib/stone/core_ext/symbol.rb
|
35
|
+
has_rdoc: false
|
36
|
+
homepage: http://github.com/bomberstudios/stone/
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.8.5
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.2.0
|
58
|
+
signing_key:
|
59
|
+
specification_version: 2
|
60
|
+
summary: Super-simple data persistence layer created for small applications.
|
61
|
+
test_files: []
|
62
|
+
|