yaml_record4 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +22 -0
- data/README.md +180 -0
- data/Rakefile +10 -0
- data/lib/yaml_record/base.rb +388 -0
- data/lib/yaml_record/version.rb +3 -0
- data/lib/yaml_record.rb +9 -0
- data/test/base_test.rb +193 -0
- data/test/test_helper.rb +17 -0
- data/yaml_record.gemspec +26 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fbf3ac61a8c2cad6bc2bd097c5e08c0fd02c2f82
|
4
|
+
data.tar.gz: 4e4cea5900e52aa4885093b69bee89215322331a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 138dfcfb209574bef2735f56731eafdf24c9374135d067e50a3d5a928caeee25b20996ee538c89ba2f41f1ebaa5342693768544c581cdf0c85423349ed32ba27
|
7
|
+
data.tar.gz: 5ae126e0f749cb9825b077cf0545db92109f217e3d24db6f3321ad0e0b2f0ce26c0b2f4d50655cdd9f1305e8fd84fb512304026f180caf32dc7eb3713f9ea0a3
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use ruby-1.9.2-p180@sandbox #> /dev/null
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011, Nico Taing, Miso, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# YAML RECORD #
|
2
|
+
|
3
|
+
## Introduction ##
|
4
|
+
|
5
|
+
YAML Record is a data persistence library that complies with the ActiveModel API. Using YAMLRecord should be familiar to anyone that has used ActiveRecord before to manage your database. Using this library, the data is persisted in a YAML backed file.
|
6
|
+
|
7
|
+
## Rationale ##
|
8
|
+
|
9
|
+
*Why a YAML-based persistence store?* In certain situations, there are collections of simple data in which there are very few records which are by nature infrequently accessed and that are ideally able to be scanned easily within a text file. These can include a simple contact form, landing page interest, feedback forms, surveys, team pages, etc where there is simply no need for the overhead of a fully persisted database solution.
|
10
|
+
|
11
|
+
There are many cases where YAMLRecord is **not the correct** persistence strategy. Any collection that is going to have substantial number of records, will be frequently updated, or is accessible by a large volume of users should not be stored in a YAML text file for obvious reasons. However, for specific cases, the convenience of storing things in a simple text file becomes apparent. Being able to access the text file data as if the records were in a familiar database ORM has many conveniences and advantages such as keeping the controllers standard and leveraging existing ORM knowledge.
|
12
|
+
|
13
|
+
## Installation ##
|
14
|
+
|
15
|
+
Install using rubygems:
|
16
|
+
|
17
|
+
gem install yaml_record
|
18
|
+
|
19
|
+
Or add gem to your Gemfile:
|
20
|
+
|
21
|
+
# Gemfile
|
22
|
+
gem 'yaml_record'
|
23
|
+
|
24
|
+
## Usage ##
|
25
|
+
|
26
|
+
### Declaration ###
|
27
|
+
|
28
|
+
Create any ruby object and inherit from `YamlRecord:Base` to define a type:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class Post < YamlRecord::Base
|
32
|
+
# Declare your properties
|
33
|
+
properties :title, :body, :user_id
|
34
|
+
|
35
|
+
# Declare source file path
|
36
|
+
source Rails.root.join("config/posts")
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Use this new object the same way as any ActiveRecord object.
|
41
|
+
|
42
|
+
### Retrieval ###
|
43
|
+
|
44
|
+
Retrieve the collection:
|
45
|
+
|
46
|
+
Post.all => [@p1, @p2]
|
47
|
+
|
48
|
+
Retrieve item by id:
|
49
|
+
|
50
|
+
Post.find("a1b2") => @p1
|
51
|
+
|
52
|
+
Retrieve by attribute:
|
53
|
+
|
54
|
+
Post.find_by_attribute(:title, "some title") => @p
|
55
|
+
|
56
|
+
### Create ###
|
57
|
+
|
58
|
+
Initialize post:
|
59
|
+
|
60
|
+
@p = Post.new(:title => "...", :body => "...", :user_id => 5)
|
61
|
+
@p.save
|
62
|
+
|
63
|
+
Create post:
|
64
|
+
|
65
|
+
@p = Post.create(:title => "...", :body => "...", :user_id => 6)
|
66
|
+
|
67
|
+
### Update ###
|
68
|
+
|
69
|
+
Update attributes using the expected method:
|
70
|
+
|
71
|
+
@p.update_attributes(:title => "new title")
|
72
|
+
|
73
|
+
### Destroy ###
|
74
|
+
|
75
|
+
Destroy a given record:
|
76
|
+
|
77
|
+
@p.destroy
|
78
|
+
|
79
|
+
### Access ###
|
80
|
+
|
81
|
+
Access attributes:
|
82
|
+
|
83
|
+
@p = Post.find("a1b2")
|
84
|
+
@p.title => "..."
|
85
|
+
|
86
|
+
Assign attributes:
|
87
|
+
|
88
|
+
@p.title = "new title"
|
89
|
+
@p.save
|
90
|
+
|
91
|
+
### Callbacks ###
|
92
|
+
|
93
|
+
Create callbacks:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class Submission < YamlRecord::Base
|
97
|
+
# ...
|
98
|
+
before_create :do_something # or before_save, before_destroy, ...
|
99
|
+
|
100
|
+
def do_something
|
101
|
+
# something here
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
## Example ##
|
107
|
+
|
108
|
+
Imagine a simple contact form that accepts a name and email from a user along with a body:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class Submission < YamlRecord::Base
|
112
|
+
# Declare your properties
|
113
|
+
properties :name, :email, :body
|
114
|
+
|
115
|
+
# Declare source file path (config/contact.yml)
|
116
|
+
source Rails.root.join("config/contact")
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
Once we define the Contact model, we can setup a controller and form just the same as in ActiveRecord:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class SubmissionsController < AC::Base
|
124
|
+
def create
|
125
|
+
@submission = Submission.create(params[:submission])
|
126
|
+
end
|
127
|
+
|
128
|
+
def index
|
129
|
+
@submissions = Submission.all
|
130
|
+
end
|
131
|
+
|
132
|
+
def show
|
133
|
+
@submission = Submission.find(params[:id])
|
134
|
+
end
|
135
|
+
|
136
|
+
def update
|
137
|
+
@submission = Submission.find(params[:id])
|
138
|
+
@submission.update_attributes(params[:submission])
|
139
|
+
end
|
140
|
+
|
141
|
+
def destroy
|
142
|
+
@submission = Submission.find(params[:id])
|
143
|
+
@submission.destroy
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
As you can see the controller appears the same as any ActiveRecord controller would and this makes managing the YAML data easy and convenient. You can even define callbacks in your object as you would in ActiveRecord:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
class Submission < YamlRecord::Base
|
152
|
+
# ...
|
153
|
+
before_create :do_something # or before_save, before_destroy, ...
|
154
|
+
|
155
|
+
def do_something
|
156
|
+
# something here
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
And that's all! Each record will be persisted to the source file for easy access.
|
162
|
+
|
163
|
+
## Issues ##
|
164
|
+
|
165
|
+
* Validations should be supported `validates_presence_of :name`
|
166
|
+
* Property type declarations should be available `property :age, Integer`
|
167
|
+
|
168
|
+
## Contributors ##
|
169
|
+
|
170
|
+
Created at Miso by Nico Taing and Nathan Esquenazi
|
171
|
+
|
172
|
+
Contributors and patches are welcome! Please send a pull request!
|
173
|
+
|
174
|
+
## Notes ##
|
175
|
+
|
176
|
+
There is already an excellent project for YAML persistence if you are using [Datamapper](https://github.com/datamapper/dm-yaml-adapter). In the situation in which we were using DM and [Padrino](http://padrinorb.com), this would surely be a better choice. But if you are using ActiveRecord and Rails, this library is a lightweight and standalone solution.
|
177
|
+
|
178
|
+
## License ##
|
179
|
+
|
180
|
+
YAML Record is Copyright © 2011 Nico Taing, Miso. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,388 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
#require 'active_support'
|
7
|
+
require 'active_support/core_ext/kernel'
|
8
|
+
require 'active_support/core_ext/class'
|
9
|
+
require 'active_support/core_ext/hash'
|
10
|
+
|
11
|
+
require 'active_model'
|
12
|
+
|
13
|
+
module YamlRecord
|
14
|
+
class Base
|
15
|
+
include ActiveModel::Validations
|
16
|
+
extend ActiveModel::Naming # Required dependency for ActiveModel::Errors
|
17
|
+
extend ActiveModel::Callbacks
|
18
|
+
|
19
|
+
define_model_callbacks :save, :create, :destroy, :only => [:after, :before]
|
20
|
+
|
21
|
+
before_create :set_id!
|
22
|
+
|
23
|
+
attr_accessor :attributes, :is_created, :is_destroyed
|
24
|
+
attr_reader :errors
|
25
|
+
|
26
|
+
# Constructs a new YamlRecord instance based on specified attribute hash
|
27
|
+
#
|
28
|
+
# === Example:
|
29
|
+
#
|
30
|
+
# class Post < YamlRecord::Base; properties :foo; end
|
31
|
+
#
|
32
|
+
# Post.new(:foo => "bar")
|
33
|
+
#
|
34
|
+
def initialize(attr_hash={})
|
35
|
+
attr_hash.symbolize_keys!
|
36
|
+
attr_hash.reverse_merge!(self.class.properties.inject({}) { |result, key| result[key] = nil; result })
|
37
|
+
|
38
|
+
self.attributes ||= {}
|
39
|
+
self.is_created = attr_hash.delete(:persisted) || false
|
40
|
+
attr_hash.each do |k,v|
|
41
|
+
self.send("#{k}=", v) # self.attributes[:media] = "foo"
|
42
|
+
end
|
43
|
+
|
44
|
+
@errors = ActiveModel::Errors.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Accesses given attribute from YamlRecord instance
|
48
|
+
#
|
49
|
+
# === Example:
|
50
|
+
#
|
51
|
+
# @post[:foo] => "bar"
|
52
|
+
#
|
53
|
+
def [](attribute)
|
54
|
+
self.attributes[attribute]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Assign given attribute from YamlRecord instance with specified value
|
58
|
+
#
|
59
|
+
# === Example:
|
60
|
+
#
|
61
|
+
# @post[:foo] = "baz"
|
62
|
+
#
|
63
|
+
def []=(attribute, value)
|
64
|
+
self.attributes[attribute] = value
|
65
|
+
end
|
66
|
+
|
67
|
+
# Saved YamlRecord instance to file
|
68
|
+
# Executes save and create callbacks
|
69
|
+
# Returns true if record saved; false otherwise
|
70
|
+
#
|
71
|
+
# === Example:
|
72
|
+
#
|
73
|
+
# @post.save => true
|
74
|
+
#
|
75
|
+
def save
|
76
|
+
block = lambda do
|
77
|
+
run_callbacks(:save) do
|
78
|
+
existing_items = self.class.all
|
79
|
+
if self.new_record?
|
80
|
+
existing_items << self
|
81
|
+
else # update existing record
|
82
|
+
updated_item = existing_items.find { |item| item.id == self.id }
|
83
|
+
return false unless updated_item
|
84
|
+
updated_item.attributes = self.attributes
|
85
|
+
end
|
86
|
+
|
87
|
+
raw_data = existing_items ? existing_items.map { |item| item.persisted_attributes } : []
|
88
|
+
self.class.write_contents(raw_data) if self.valid?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
self.is_created ? block.call : run_callbacks(:create) { block.call }
|
93
|
+
true
|
94
|
+
rescue IOError
|
95
|
+
false
|
96
|
+
end
|
97
|
+
|
98
|
+
# Update YamlRecord instance with specified attributes
|
99
|
+
# Returns true if record updated; false otherwise
|
100
|
+
#
|
101
|
+
# === Example:
|
102
|
+
#
|
103
|
+
# @post.update_attributes(:foo => "baz", :miso => "awesome") => true
|
104
|
+
#
|
105
|
+
def update_attributes(updated_attrs={})
|
106
|
+
updated_attrs.each { |k,v| self.send("#{k}=", v) }
|
107
|
+
self.save
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns array of instance attributes names; An attribute is a value stored for this record (persisted or not)
|
111
|
+
#
|
112
|
+
# === Example:
|
113
|
+
#
|
114
|
+
# @post.column_names => ["foo", "miso"]
|
115
|
+
#
|
116
|
+
def column_names
|
117
|
+
array = []
|
118
|
+
self.attributes.each_key { |k| array << k.to_s }
|
119
|
+
array
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns hash of attributes to be persisted to file.
|
123
|
+
# A persisted attribute is a value stored in the file (specified with the properties declaration)
|
124
|
+
#
|
125
|
+
# === Example:
|
126
|
+
#
|
127
|
+
# class Post < YamlRecord::Base; properties :foo, :miso; end
|
128
|
+
# @post = Post.create(:foo => "bar", :miso => "great")
|
129
|
+
# @post.persisted_attributes => { :id => "a1b2c3", :foo => "bar", :miso => "great" }
|
130
|
+
#
|
131
|
+
def persisted_attributes
|
132
|
+
self.attributes.slice(*self.class.properties).reject { |k, v| v.nil? }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns true if YamlRecord instance hasn't persisted; false otherwise
|
136
|
+
#
|
137
|
+
# === Example:
|
138
|
+
#
|
139
|
+
# @post = Post.new(:foo => "bar", :miso => "great")
|
140
|
+
# @post.new_record? => true
|
141
|
+
# @post.save => true
|
142
|
+
# @post.new_record? => false
|
143
|
+
#
|
144
|
+
def new_record?
|
145
|
+
!self.is_created
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns true if YamlRecord instance has been destroyed; false otherwise
|
149
|
+
#
|
150
|
+
# === Example:
|
151
|
+
#
|
152
|
+
# @post = Post.new(:foo => "bar", :miso => "great")
|
153
|
+
# @post.destroyed? => false
|
154
|
+
# @post.save
|
155
|
+
# @post.destroy => true
|
156
|
+
# @post.destroyed? => true
|
157
|
+
#
|
158
|
+
def destroyed?
|
159
|
+
self.is_destroyed
|
160
|
+
end
|
161
|
+
|
162
|
+
# Remove a persisted YamlRecord object
|
163
|
+
# Returns true if destroyed; false otherwise
|
164
|
+
#
|
165
|
+
# === Example:
|
166
|
+
#
|
167
|
+
# @post = Post.create(:foo => "bar", :miso => "great")
|
168
|
+
# Post.all.size => 1
|
169
|
+
# @post.destroy => true
|
170
|
+
# Post.all.size => 0
|
171
|
+
#
|
172
|
+
def destroy
|
173
|
+
run_callbacks(:destroy) do
|
174
|
+
new_data = self.class.all
|
175
|
+
.reject { |item| item.persisted_attributes == self.persisted_attributes }
|
176
|
+
.map { |item| item.persisted_attributes }
|
177
|
+
self.class.write_contents(new_data)
|
178
|
+
self.is_destroyed = true
|
179
|
+
end
|
180
|
+
true
|
181
|
+
rescue IOError
|
182
|
+
false
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns YamlRecord Instance
|
186
|
+
# Complies with ActiveModel api
|
187
|
+
#
|
188
|
+
# === Example:
|
189
|
+
#
|
190
|
+
# @post.to_model => @post
|
191
|
+
#
|
192
|
+
def to_model
|
193
|
+
self
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns the instance of a record as a parameter
|
197
|
+
# By default return an id
|
198
|
+
#
|
199
|
+
# === Example:
|
200
|
+
#
|
201
|
+
# @post.to_param => <id>
|
202
|
+
#
|
203
|
+
def to_param
|
204
|
+
self.id
|
205
|
+
end
|
206
|
+
|
207
|
+
# Reload YamlRecord instance attributes from file
|
208
|
+
#
|
209
|
+
# === Example:
|
210
|
+
#
|
211
|
+
# @post = Post.create(:foo => "bar", :miso => "great")
|
212
|
+
# @post.foo = "bazz"
|
213
|
+
# @post.reload
|
214
|
+
# @post.foo => "bar"
|
215
|
+
#
|
216
|
+
def reload
|
217
|
+
record = self.class.find(self.id)
|
218
|
+
self.attributes = record.attributes
|
219
|
+
record
|
220
|
+
end
|
221
|
+
|
222
|
+
# Find YamlRecord instance given attribute name and expected value
|
223
|
+
# Supports checking inclusion for array based values
|
224
|
+
# Returns instance if found; false otherwise
|
225
|
+
#
|
226
|
+
# === Example:
|
227
|
+
#
|
228
|
+
# Post.find_by_attribute(:foo, "bar") => @post
|
229
|
+
# Post.find_by_attribute(:some_list, "item") => @post
|
230
|
+
#
|
231
|
+
def self.find_by_attribute(attribute, expected_value)
|
232
|
+
self.all.find do |record|
|
233
|
+
value = record.send(attribute) if record.respond_to?(attribute)
|
234
|
+
value.is_a?(Array) ?
|
235
|
+
value.include?(expected_value) :
|
236
|
+
value == expected_value
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class << self
|
241
|
+
|
242
|
+
# Find YamlRecord instance given id
|
243
|
+
# Returns instance if found; false otherwise
|
244
|
+
#
|
245
|
+
# === Example:
|
246
|
+
#
|
247
|
+
# Post.find_by_id("a1b2c3") => @post
|
248
|
+
#
|
249
|
+
def find_by_id(value)
|
250
|
+
self.find_by_attribute(:id, value)
|
251
|
+
end
|
252
|
+
alias :find :find_by_id
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns collection of all YamlRecord instances
|
256
|
+
# Caches results during request
|
257
|
+
#
|
258
|
+
# === Example:
|
259
|
+
#
|
260
|
+
# Post.all => [@post1, @post2, ...]
|
261
|
+
# Post.all(true) => (...force reload...)
|
262
|
+
#
|
263
|
+
def self.all
|
264
|
+
begin
|
265
|
+
raw_items = YAML.load_file(source)
|
266
|
+
rescue Errno::ENOENT
|
267
|
+
ensure
|
268
|
+
raw_items ||= []
|
269
|
+
end
|
270
|
+
raw_items.map { |item| self.new(item.merge(:persisted => true)) }
|
271
|
+
end
|
272
|
+
|
273
|
+
# Find last YamlRecord instance given a limit
|
274
|
+
# Returns an array of instances if found; empty otherwise
|
275
|
+
#
|
276
|
+
# === Example:
|
277
|
+
#
|
278
|
+
# Post.last => @post6
|
279
|
+
# Post.last(3) => [@p4, @p5, @p6]
|
280
|
+
#
|
281
|
+
def self.last(limit=1)
|
282
|
+
limit == 1 ? self.all.last : self.all.last(limit)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Find first YamlRecord instance given a limit
|
286
|
+
# Returns an array of instances if found; empty otherwise
|
287
|
+
#
|
288
|
+
# === Example:
|
289
|
+
#
|
290
|
+
# Post.first => @post
|
291
|
+
# Post.first(3) => [@p1, @p2, @p3]
|
292
|
+
#
|
293
|
+
def self.first(limit=1)
|
294
|
+
limit == 1 ? self.all.first : self.all.first(limit)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Initializes YamlRecord instance given an attribute hash and saves afterwards
|
298
|
+
# Returns instance if successfully saved; false otherwise
|
299
|
+
#
|
300
|
+
# === Example:
|
301
|
+
#
|
302
|
+
# Post.create(:foo => "bar", :miso => "great") => @post
|
303
|
+
#
|
304
|
+
def self.create(attributes={})
|
305
|
+
@fs = self.new(attributes)
|
306
|
+
if @fs.save == true
|
307
|
+
@fs.is_created = true;
|
308
|
+
@fs
|
309
|
+
else
|
310
|
+
false
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Declares persisted attributes for YamlRecord class
|
315
|
+
#
|
316
|
+
# === Example:
|
317
|
+
#
|
318
|
+
# class Post < YamlRecord::Base; properties :foo, :miso; end
|
319
|
+
# Post.create(:foo => "bar", :miso => "great") => @post
|
320
|
+
#
|
321
|
+
def self.properties(*names)
|
322
|
+
@_properties ||= []
|
323
|
+
if names.size == 0 # getter
|
324
|
+
@_properties
|
325
|
+
elsif names.size > 0 # setter
|
326
|
+
names = names | [:id]
|
327
|
+
setup_properties!(*names)
|
328
|
+
@_properties += names
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Declares source file for YamlRecord class
|
333
|
+
#
|
334
|
+
# === Example:
|
335
|
+
#
|
336
|
+
# class Post < YamlRecord::Base
|
337
|
+
# source "path/to/yaml/file"
|
338
|
+
# end
|
339
|
+
#
|
340
|
+
def self.source(file = nil)
|
341
|
+
@@file ||= file ? file.to_s : File.join(YamlRecord.root_path, "#{model_name.name}.yml")
|
342
|
+
end
|
343
|
+
|
344
|
+
# Overrides equality to match if matching ids
|
345
|
+
#
|
346
|
+
def ==(comparison_record)
|
347
|
+
self.id == comparison_record.id
|
348
|
+
end
|
349
|
+
|
350
|
+
protected
|
351
|
+
|
352
|
+
# Write raw yaml data to file
|
353
|
+
# Protected method, not called during usage
|
354
|
+
#
|
355
|
+
# === Example:
|
356
|
+
#
|
357
|
+
# Post.write_content([{ :foo => "bar"}, { :foo => "baz"}, ...]) # writes to source file
|
358
|
+
#
|
359
|
+
def self.write_contents(raw_data)
|
360
|
+
File.open(self.source, 'w') {|f| f.write(raw_data.to_yaml) }
|
361
|
+
@records = nil
|
362
|
+
end
|
363
|
+
|
364
|
+
# Creates reader and writer methods for each persisted attribute
|
365
|
+
# Protected method, not called during usage
|
366
|
+
#
|
367
|
+
# === Example:
|
368
|
+
#
|
369
|
+
# Post.setup_properties!(:foo)
|
370
|
+
# @post.foo = "baz"
|
371
|
+
# @post.foo => "baz"
|
372
|
+
#
|
373
|
+
def self.setup_properties!(*names)
|
374
|
+
names.each do |name|
|
375
|
+
define_method(name) { self[name.to_sym] }
|
376
|
+
define_method("#{name}=") { |val| self[name.to_sym] = val }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Assign YamlRecord a unique id if not set
|
381
|
+
# Invoke before create of an instance
|
382
|
+
# Protected method, not called during usage
|
383
|
+
#
|
384
|
+
def set_id!
|
385
|
+
self.id ||= SecureRandom.hex(15)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
data/lib/yaml_record.rb
ADDED
data/test/base_test.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class YamlObject < YamlRecord::Base
|
4
|
+
properties :title, :body, :child_ids
|
5
|
+
source File.dirname(__FILE__) + "/../tmp/yaml_object"
|
6
|
+
end
|
7
|
+
|
8
|
+
class BaseTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@obj_title = "Simple Title"
|
12
|
+
@obj_id = "1234"
|
13
|
+
@obj2_id = "5678"
|
14
|
+
@obj2_title = "Simple Title 2"
|
15
|
+
|
16
|
+
@attr = {
|
17
|
+
:child_ids => [@obj_id],
|
18
|
+
:title => @obj_title,
|
19
|
+
:body => "Body!!"
|
20
|
+
}
|
21
|
+
|
22
|
+
@attr2 = {
|
23
|
+
:child_ids => [@obj2_id],
|
24
|
+
:title => @obj2_title,
|
25
|
+
:body => "Body!!"
|
26
|
+
}
|
27
|
+
|
28
|
+
clean_yaml_record(YamlObject)
|
29
|
+
@fs = YamlObject.create(@attr)
|
30
|
+
end
|
31
|
+
|
32
|
+
context "for instance methods" do
|
33
|
+
|
34
|
+
context "for [] method" do
|
35
|
+
should("get attribute with [attribute]"){ assert_equal @fs.title, @fs[:title] }
|
36
|
+
end
|
37
|
+
|
38
|
+
context "for []= method" do
|
39
|
+
setup do
|
40
|
+
@fs[:title] = "Toto"
|
41
|
+
end
|
42
|
+
should("set attribute with [attribute]="){ assert_equal @fs[:title], "Toto" }
|
43
|
+
end
|
44
|
+
|
45
|
+
context "for save method" do
|
46
|
+
setup do
|
47
|
+
@fs2 = YamlObject.new(@attr)
|
48
|
+
@fs2.save
|
49
|
+
end
|
50
|
+
|
51
|
+
should("save on yaml file"){ assert_equal YamlObject.last.attributes.diff(@attr), {:id => @fs2.reload.id } }
|
52
|
+
end
|
53
|
+
|
54
|
+
context "for update_attributes method" do
|
55
|
+
setup do
|
56
|
+
@fs.update_attributes(:title => "Toto", :body => "http://somewhereelse.com")
|
57
|
+
@fs.reload
|
58
|
+
end
|
59
|
+
should("update title") { assert_equal @fs.title, "Toto" }
|
60
|
+
should("update body") { assert_equal @fs.body, "http://somewhereelse.com" }
|
61
|
+
end
|
62
|
+
|
63
|
+
context "for column_names method" do
|
64
|
+
should("return an array with attributes names") { assert_equal @fs.column_names.sort!, YamlObject.properties.map { |p| p.to_s }.sort! }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "for persisted_attributes method" do
|
68
|
+
should("return persisted attributes") { assert_equal [:title, :body, :child_ids, :id ].sort_by {|sym| sym.to_s}, @fs.persisted_attributes.keys.sort_by {|sym| sym.to_s} }
|
69
|
+
end
|
70
|
+
|
71
|
+
context "for new_record? method" do
|
72
|
+
setup do
|
73
|
+
@fs3 = YamlObject.new
|
74
|
+
end
|
75
|
+
should("be a new record") { assert @fs3.new_record? }
|
76
|
+
should("not be a new record") { assert_false @fs.new_record? }
|
77
|
+
end
|
78
|
+
|
79
|
+
context "for destroyed? method" do
|
80
|
+
setup do
|
81
|
+
@fs4 = YamlObject.create(@attr)
|
82
|
+
@fs4.destroy
|
83
|
+
end
|
84
|
+
should("be a destoyed") { assert @fs4.destroyed? }
|
85
|
+
should("not be destroyed") { assert_false @fs.destroyed? }
|
86
|
+
end
|
87
|
+
|
88
|
+
context "for destroy method" do
|
89
|
+
setup do
|
90
|
+
@fs5 = YamlObject.create(@attr)
|
91
|
+
@fs5.destroy
|
92
|
+
end
|
93
|
+
should("not find @fs5"){ assert_nil YamlObject.find(@fs5.id) }
|
94
|
+
end
|
95
|
+
|
96
|
+
context "for reload method" do
|
97
|
+
setup do
|
98
|
+
@fs.title = "Foo"
|
99
|
+
end
|
100
|
+
should("equal to Foo"){ assert_equal @fs.title, "Foo" }
|
101
|
+
should("equal to correct title"){ assert_equal @fs.reload.title, @obj_title }
|
102
|
+
end
|
103
|
+
|
104
|
+
context "for to_param method" do
|
105
|
+
setup { @fs.id = "a1b2c3" }
|
106
|
+
|
107
|
+
should("return id of record") { assert_equal(@fs.to_param, @fs.id) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "for class methods" do
|
112
|
+
context "for self.find_by_attribute method" do
|
113
|
+
setup do
|
114
|
+
@fs_found = YamlObject.find_by_attribute(:title, @obj_title)
|
115
|
+
end
|
116
|
+
should("be same object as @fs"){ assert_equal @fs_found, YamlObject.find(@fs.id) }
|
117
|
+
end
|
118
|
+
|
119
|
+
context "for self.find_by_id method" do
|
120
|
+
setup do
|
121
|
+
@fs_found = YamlObject.find_by_id(@fs.id)
|
122
|
+
@fs_found2 = YamlObject.find(@fs.id)
|
123
|
+
end
|
124
|
+
should("be same object as @fs"){ assert_equal @fs.attributes, @fs_found.attributes }
|
125
|
+
should("be same object as @fs bis"){ assert_equal @fs.attributes, @fs_found2.attributes }
|
126
|
+
end
|
127
|
+
|
128
|
+
context "for self.all method" do
|
129
|
+
setup do
|
130
|
+
clean_yaml_record(YamlObject)
|
131
|
+
@fs, @fs2 = YamlObject.create(@attr), YamlObject.create(@attr2)
|
132
|
+
end
|
133
|
+
should("retrieve 2 YamlObject obj"){ assert_equal YamlObject.all.size, 2 }
|
134
|
+
should("return as first item @fs"){ assert_equal YamlObject.all.first.attributes, @fs.attributes }
|
135
|
+
should("return as last item @fs2"){ assert_equal YamlObject.all.last.attributes, @fs2.attributes }
|
136
|
+
end
|
137
|
+
|
138
|
+
context "for self.first method" do
|
139
|
+
setup do
|
140
|
+
clean_yaml_record(YamlObject)
|
141
|
+
@fs, @fs2 = YamlObject.create(@attr), YamlObject.create(@attr2)
|
142
|
+
end
|
143
|
+
|
144
|
+
should("return @fs as the first item"){ assert_equal YamlObject.first.attributes, @fs.attributes }
|
145
|
+
should("return @fs"){ assert_equal YamlObject.first(2).first.attributes, @fs.attributes }
|
146
|
+
should("return @fs2"){ assert_equal YamlObject.first(2).last.attributes, @fs2.attributes }
|
147
|
+
end
|
148
|
+
|
149
|
+
context "for self.last method" do
|
150
|
+
setup do
|
151
|
+
clean_yaml_record(YamlObject)
|
152
|
+
@fs, @fs2 = YamlObject.create(@attr), YamlObject.create(@attr2)
|
153
|
+
end
|
154
|
+
|
155
|
+
should("return @fs as the first item"){ assert_equal YamlObject.last.attributes, @fs2.attributes }
|
156
|
+
should("return @fs"){ assert_equal YamlObject.last(2).first.attributes, @fs.attributes }
|
157
|
+
should("return @fs2"){ assert_equal YamlObject.last(2).last.attributes, @fs2.attributes }
|
158
|
+
end
|
159
|
+
|
160
|
+
context "for self.write_contents method" do
|
161
|
+
setup do
|
162
|
+
clean_yaml_record(YamlObject)
|
163
|
+
@attributes = [ @attr, @attr2 ]
|
164
|
+
YamlObject.write_contents(@attributes)
|
165
|
+
end
|
166
|
+
should("write in yaml file"){ assert_equal YAML.load_file(YamlObject.source), [ @attr, @attr2 ] }
|
167
|
+
end
|
168
|
+
|
169
|
+
context "for self.create method" do
|
170
|
+
setup do
|
171
|
+
clean_yaml_record(YamlObject)
|
172
|
+
@fs = YamlObject.create(@attr)
|
173
|
+
@fs_not_created = YamlObject.new(@attr)
|
174
|
+
end
|
175
|
+
should("create @fs"){ assert_equal YamlObject.last.attributes, @fs.attributes }
|
176
|
+
should("set its is_created to true"){ assert @fs.is_created }
|
177
|
+
should("set @fs_not_created is_created field to false"){ assert_false @fs_not_created.is_created }
|
178
|
+
end
|
179
|
+
|
180
|
+
context "for set_id!" do
|
181
|
+
setup do
|
182
|
+
@fs_no_id = YamlObject.new(@attr)
|
183
|
+
@fs_with_id = YamlObject.create(@attr)
|
184
|
+
@id = @fs_with_id.id
|
185
|
+
@fs_with_id.update_attributes(:title => "Gomiso")
|
186
|
+
end
|
187
|
+
should("not have any id"){ assert_nil @fs_no_id.id }
|
188
|
+
should("have a id"){ assert @fs_with_id.id }
|
189
|
+
should("keep the same id"){ assert_equal @fs_with_id.id, @id }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
#require 'test/unit'
|
4
|
+
require 'shoulda'
|
5
|
+
require File.dirname(__FILE__) + "/../lib/yaml_record"
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
def clean_yaml_record(class_record)
|
9
|
+
File.open(class_record.source, 'w') {|f| f.write(nil) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Asserts that the condition is not true
|
13
|
+
# assert_false @title == "hey"
|
14
|
+
def assert_false(condition, message='')
|
15
|
+
assert !condition, message
|
16
|
+
end
|
17
|
+
end
|
data/yaml_record.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "yaml_record/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "yaml_record4"
|
7
|
+
s.version = YamlRecord::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Nico Taing", "Nathan Esquenazi", "Thierry Zires"]
|
10
|
+
s.email = ["zshuaibin@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/zires/yaml_record/tree/rails41"
|
12
|
+
s.summary = %q{YAML file persistence engine}
|
13
|
+
s.description = %q{Use YAML for persisted data with ActiveModel interface}
|
14
|
+
|
15
|
+
#s.rubyforge_project = "yaml_record"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_dependency 'activesupport', '~>4.1'
|
23
|
+
s.add_dependency 'activemodel', '~>4.1'
|
24
|
+
s.add_development_dependency 'rake', '~>0.9.2'
|
25
|
+
s.add_development_dependency 'shoulda'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yaml_record4
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nico Taing
|
8
|
+
- Nathan Esquenazi
|
9
|
+
- Thierry Zires
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2014-09-01 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '4.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '4.1'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: activemodel
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '4.1'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '4.1'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: rake
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ~>
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.9.2
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.9.2
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: shoulda
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
description: Use YAML for persisted data with ActiveModel interface
|
72
|
+
email:
|
73
|
+
- zshuaibin@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- .gitignore
|
79
|
+
- .rvmrc
|
80
|
+
- Gemfile
|
81
|
+
- MIT-LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/yaml_record.rb
|
85
|
+
- lib/yaml_record/base.rb
|
86
|
+
- lib/yaml_record/version.rb
|
87
|
+
- test/base_test.rb
|
88
|
+
- test/test_helper.rb
|
89
|
+
- yaml_record.gemspec
|
90
|
+
homepage: https://github.com/zires/yaml_record/tree/rails41
|
91
|
+
licenses: []
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.1.11
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: YAML file persistence engine
|
113
|
+
test_files:
|
114
|
+
- test/base_test.rb
|
115
|
+
- test/test_helper.rb
|
116
|
+
has_rdoc:
|