yaml_record_rails_4 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7c8d16d47aacc32b9e9872265a47b59692127196
4
+ data.tar.gz: f5aa8767e80cf7b8d82a69e9dae554e6a3073c6a
5
+ SHA512:
6
+ metadata.gz: 649b1d603d11fcbc1ca96795f97f8db64ddea172989d50bc3b0dc648b49f8240bf160916fb5dc93ce43ec57101ac16277cc7a015171eb85afb31b9bdd80ddf0b
7
+ data.tar.gz: 843b4a98521e65482e2dfaafcbb2843546b2b845d274d78a7fce68d433b224faaf8c37babdc41433130bcf98c1ff113c4c6ada2e79b5542d50e2fb83a2de1893
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in yaml_record.gemspec
4
+ gemspec
@@ -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.
@@ -0,0 +1,212 @@
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
+ ```bash
18
+ gem install yaml_record
19
+ ```
20
+
21
+ Or add gem to your Gemfile:
22
+
23
+ ```ruby
24
+ # Gemfile
25
+ gem 'yaml_record'
26
+ # OR if you're using Rails 3.1
27
+ gem 'yaml_record' :git => "git@github.com:Nico-Taing/yaml_record.git", :branch => "rails31"
28
+ ```
29
+
30
+ ## Usage ##
31
+
32
+ ### Declaration ###
33
+
34
+ Create any ruby object and inherit from `YamlRecord:Base` to define a type:
35
+
36
+ ```ruby
37
+ class Post < YamlRecord::Base
38
+ # Declare your properties
39
+ properties :title, :body, :user_id
40
+
41
+ # Declare your adapter (local by default)
42
+ adapter :local # or :redis
43
+
44
+ # Declare source file path
45
+ source Rails.root.join("config/posts")
46
+ end
47
+ ```
48
+
49
+ Use this new object the same way as any ActiveRecord object.
50
+
51
+ ### Retrieval ###
52
+
53
+ Retrieve the collection:
54
+
55
+ Post.all => [@p1, @p2]
56
+
57
+ Retrieve item by id:
58
+
59
+ Post.find("a1b2") => @p1
60
+
61
+ Retrieve by attribute:
62
+
63
+ Post.find_by_attribute(:title, "some title") => @p
64
+
65
+ ### Create ###
66
+
67
+ Initialize post:
68
+
69
+ @p = Post.new(:title => "...", :body => "...", :user_id => 5)
70
+ @p.save
71
+
72
+ Create post:
73
+
74
+ @p = Post.create(:title => "...", :body => "...", :user_id => 6)
75
+
76
+ ### Update ###
77
+
78
+ Update attributes using the expected method:
79
+
80
+ @p.update_attributes(:title => "new title")
81
+
82
+ ### Destroy ###
83
+
84
+ Destroy a given record:
85
+
86
+ @p.destroy
87
+
88
+ ### Access ###
89
+
90
+ Access attributes:
91
+
92
+ @p = Post.find("a1b2")
93
+ @p.title => "..."
94
+
95
+ Assign attributes:
96
+
97
+ @p.title = "new title"
98
+ @p.save
99
+
100
+ ### Callbacks ###
101
+
102
+ Create callbacks:
103
+
104
+ ```ruby
105
+ class Submission < YamlRecord::Base
106
+ # ...
107
+ before_create :do_something # or before_save, before_destroy, ...
108
+
109
+ def do_something
110
+ # something here
111
+ end
112
+ end
113
+ ```
114
+
115
+ ## Storage Adapters ##
116
+
117
+ YAMLRecord supports pluggable storage adapters that control the storage engine used for the YAML data. By default, the adapter used is the
118
+ `local` store which writes a file (specified by `source` path) to the local system. There are currently two available adapters: `Local` and `Redis`.
119
+
120
+ To configure the adapter, you can simply declare within the object:
121
+
122
+ ```ruby
123
+ class Submission < YamlRecord::Base
124
+ adapter :redis, $redis # Second parameter is the redis client instance
125
+ source "contacts" # stores yaml namespaced as 'yaml_record:contacts' in redis
126
+ end
127
+ ```
128
+
129
+ Each storage adapter only defines a `read` and `write` interface and is easy to create.
130
+ Checkout the [redis adapter](https://github.com/Nico-Taing/yaml_record/blob/master/lib/yaml_record/adapters/redis_store.rb) for an example of how simple they are to define.
131
+ Feel free to create additional adapters and send them to us via a pull request.
132
+
133
+ ## Example ##
134
+
135
+ Imagine a simple contact form that accepts a name and email from a user along with a body:
136
+
137
+ ```ruby
138
+ class Submission < YamlRecord::Base
139
+ # Declare your properties
140
+ properties :name, :email, :body
141
+
142
+ # Declare your adapter (local by default)
143
+ adapter :local # or :redis
144
+
145
+ # Declare source file path (config/contact.yml)
146
+ source Rails.root.join("config/contact")
147
+ end
148
+ ```
149
+
150
+ Once we define the Contact model, we can setup a controller and form just the same as in ActiveRecord:
151
+
152
+ ```ruby
153
+ class SubmissionsController < AC::Base
154
+ def create
155
+ @submission = Submission.create(params[:submission])
156
+ end
157
+
158
+ def index
159
+ @submissions = Submission.all
160
+ end
161
+
162
+ def show
163
+ @submission = Submission.find(params[:id])
164
+ end
165
+
166
+ def update
167
+ @submission = Submission.find(params[:id])
168
+ @submission.update_attributes(params[:submission])
169
+ end
170
+
171
+ def destroy
172
+ @submission = Submission.find(params[:id])
173
+ @submission.destroy
174
+ end
175
+ end
176
+ ```
177
+
178
+ 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:
179
+
180
+ ```ruby
181
+ class Submission < YamlRecord::Base
182
+ # ...
183
+ before_create :do_something # or before_save, before_destroy, ...
184
+
185
+ def do_something
186
+ # something here
187
+ end
188
+ end
189
+ ```
190
+
191
+ And that's all! Each record will be persisted to the source file for easy access.
192
+
193
+ ## Issues ##
194
+
195
+ * Validations should be supported `validates_presence_of :name`
196
+ * Property type declarations should be available `property :age, Integer`
197
+
198
+ ## Contributors ##
199
+
200
+ Created at Miso by Nico Taing and Nathan Esquenazi
201
+
202
+ Special thanks to [Vaudoc](https://github.com/vaudoc)
203
+
204
+ Contributors and patches are welcome! Please send a pull request!
205
+
206
+ ## Notes ##
207
+
208
+ 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.
209
+
210
+ ## License ##
211
+
212
+ 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.
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ include Rake::DSL
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new("test") do |test|
8
+ test.pattern = "test/**/*_test.rb"
9
+ test.verbose = true
10
+ end
@@ -0,0 +1,40 @@
1
+ # YAML Adapter for using a local file store
2
+
3
+ module YamlRecord
4
+ module Adapters
5
+ class LocalStore
6
+
7
+ # Returns YAML File as ruby collection
8
+ #
9
+ # === Example:
10
+ #
11
+ # @adapter.read("foo") => [{...}, {...}]
12
+ #
13
+ def read(source, dynamic_source, source_file_name)
14
+ dir_path = source + dynamic_source
15
+ full_path = dir_path + source_file_name
16
+
17
+ Dir::mkdir(dir_path) unless File.directory?(dir_path)
18
+ File.open(full_path, 'w') unless File.exists?(full_path)
19
+
20
+ YAML.load_file(source + dynamic_source + source_file_name)
21
+ end
22
+
23
+ # Writes ruby collection as YAML File
24
+ #
25
+ # === Example:
26
+ #
27
+ # @adapter.write("foo", [{...}, {...}]) => "<yaml data>"
28
+ #
29
+ def write(source, source_file_name, dynamic_source, collection)
30
+ dir_path = source + dynamic_source
31
+ full_path = dir_path + source_file_name
32
+
33
+ Dir::mkdir(dir_path) unless File.directory?(dir_path)
34
+ File.open(full_path, 'w') unless File.exists?(full_path)
35
+
36
+ File.open(source + dynamic_source + source_file_name, 'w') {|f| f.write(collection.to_yaml) }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ # YAML Adapter for using a redis client store
2
+ # YamlRecord::Adapters::RedisStore.new(@redis)
3
+
4
+ module YamlRecord
5
+ module Adapters
6
+ class RedisStore
7
+ attr_reader :client
8
+
9
+ # client is an instantiated redis client (i.e Redis::Client.new(...))
10
+ def initialize(client)
11
+ raise "Please specify a Redis client" unless client
12
+ @client = client
13
+ end
14
+
15
+ # Returns Redis YML data as ruby collection
16
+ #
17
+ # === Example:
18
+ #
19
+ # @adapter.read("foo") => [{...}, {...}]
20
+ #
21
+ def read(source)
22
+ YAML.load(@client.get(redis_key(source)).to_s)
23
+ end
24
+
25
+ # Writes ruby collection as Redis YML data
26
+ #
27
+ # === Example:
28
+ #
29
+ # @adapter.write("foo", [{...}, {...}]) => "<yaml data>"
30
+ #
31
+ def write(source, collection)
32
+ @client.set(redis_key(source), collection.to_yaml)
33
+ end
34
+
35
+ private
36
+
37
+ def redis_key(source)
38
+ "yaml_record:" + source
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,425 @@
1
+ require 'active_model'
2
+
3
+ module YamlRecord
4
+ class Base
5
+ attr_accessor :attributes, :is_created, :is_destroyed
6
+
7
+ include ActiveModel::Conversion
8
+ include ActiveModel::Validations
9
+ include ActiveModel::Callbacks
10
+ include ActiveModel::Validations::Callbacks
11
+
12
+ define_model_callbacks :create, :save, :destroy, :update, :validation, :initialize
13
+
14
+ before_create :set_id!
15
+
16
+ # Constructs a new YamlRecord instance based on specified attribute hash
17
+ #
18
+ # === Example:
19
+ #
20
+ # class Post < YamlRecord::Base; properties :foo; end
21
+ #
22
+ # Post.new(:foo => "bar")
23
+ #
24
+ def initialize(attr_hash={})
25
+ attr_hash.symbolize_keys!
26
+ attr_hash.reverse_merge!(self.class.properties.inject({}) { |result, key| result[key] = nil; result })
27
+
28
+ self.attributes ||= {}
29
+ self.is_created = attr_hash.delete(:persisted) || false
30
+ attr_hash.each do |k,v|
31
+ self.send("#{k}=", v) # self.attributes[:media] = "foo"
32
+ end
33
+ end
34
+
35
+ # Accesses given attribute from YamlRecord instance
36
+ #
37
+ # === Example:
38
+ #
39
+ # @post[:foo] => "bar"
40
+ #
41
+ def [](attribute)
42
+ self.attributes[attribute]
43
+ end
44
+
45
+ # Assign given attribute from YamlRecord instance with specified value
46
+ #
47
+ # === Example:
48
+ #
49
+ # @post[:foo] = "baz"
50
+ #
51
+ def []=(attribute, value)
52
+ self.attributes[attribute] = value
53
+ end
54
+
55
+ # Saved YamlRecord instance to file
56
+ # Executes save and create callbacks
57
+ # Returns true if record saved; false otherwise
58
+ #
59
+ # === Example:
60
+ #
61
+ # @post.save => true
62
+ #
63
+ def save
64
+ run_callbacks(:save) { false }
65
+ run_callbacks(:create) { false } unless self.is_created
66
+
67
+ existing_items = self.class.all(dynamic_source)
68
+ if self.new_record?
69
+ existing_items << self
70
+ else # update existing record
71
+ updated_item = existing_items.find { |item| item.id == self.id }
72
+ return false unless updated_item
73
+ updated_item.attributes = self.attributes
74
+ end
75
+
76
+ raw_data = existing_items ? existing_items.map { |item| item.persisted_attributes } : []
77
+ self.class.write_contents(dynamic_source, raw_data) if self.valid?
78
+
79
+ run_callbacks(:create) { true } unless self.is_created
80
+ run_callbacks(:save) { true }
81
+ true
82
+ rescue IOError
83
+ false
84
+ end
85
+
86
+ # Update YamlRecord instance with specified attributes
87
+ # Returns true if record updated; false otherwise
88
+ #
89
+ # === Example:
90
+ #
91
+ # @post.update_attributes(:foo => "baz", :miso => "awesome") => true
92
+ #
93
+ def update_attributes(updated_attrs={})
94
+ updated_attrs.each { |k,v| self.send("#{k}=", v) }
95
+ self.save
96
+ end
97
+
98
+ # Returns array of instance attributes names; An attribute is a value stored for this record (persisted or not)
99
+ #
100
+ # === Example:
101
+ #
102
+ # @post.column_names => ["foo", "miso"]
103
+ #
104
+ def column_names
105
+ array = []
106
+ self.attributes.each_key { |k| array << k.to_s }
107
+ array
108
+ end
109
+
110
+ # Returns hash of attributes to be persisted to file.
111
+ # A persisted attribute is a value stored in the file (specified with the properties declaration)
112
+ #
113
+ # === Example:
114
+ #
115
+ # class Post < YamlRecord::Base; properties :foo, :miso; end
116
+ # @post = Post.create(:foo => "bar", :miso => "great")
117
+ # @post.persisted_attributes => { :id => "a1b2c3", :foo => "bar", :miso => "great" }
118
+ #
119
+ def persisted_attributes
120
+ self.attributes.slice(*self.class.properties).reject { |k, v| v.nil? }
121
+ end
122
+
123
+ # Returns true if YamlRecord instance hasn't persisted; false otherwise
124
+ #
125
+ # === Example:
126
+ #
127
+ # @post = Post.new(:foo => "bar", :miso => "great")
128
+ # @post.new_record? => true
129
+ # @post.save => true
130
+ # @post.new_record? => false
131
+ #
132
+ def new_record?
133
+ !self.is_created
134
+ end
135
+
136
+ # Returns true if YamlRecord instance has been destroyed; false otherwise
137
+ #
138
+ # === Example:
139
+ #
140
+ # @post = Post.new(:foo => "bar", :miso => "great")
141
+ # @post.destroyed? => false
142
+ # @post.save
143
+ # @post.destroy => true
144
+ # @post.destroyed? => true
145
+ #
146
+ def destroyed?
147
+ self.is_destroyed
148
+ end
149
+
150
+ # Remove a persisted YamlRecord object
151
+ # Returns true if destroyed; false otherwise
152
+ #
153
+ # === Example:
154
+ #
155
+ # @post = Post.create(:foo => "bar", :miso => "great")
156
+ # Post.all.size => 1
157
+ # @post.destroy => true
158
+ # Post.all.size => 0
159
+ #
160
+ def destroy
161
+ run_callbacks(:destroy) { false }
162
+ new_data = self.class.all.reject { |item| item.persisted_attributes == self.persisted_attributes }.map { |item| item.persisted_attributes }
163
+ self.class.write_contents(dynamic_source, new_data)
164
+ self.is_destroyed = true
165
+ run_callbacks(:destroy) { true }
166
+ true
167
+ rescue IOError
168
+ false
169
+ end
170
+
171
+ # Execute validations for instance
172
+ # Returns true if record is valid; false otherwise
173
+ # TODO Implement validation
174
+ #
175
+ # === Example:
176
+ #
177
+ # @post.valid? => true
178
+ #
179
+ def valid?
180
+ true
181
+ end
182
+
183
+ # Returns errors messages if record isn't valid; empty array otherwise
184
+ # TODO Implement validation
185
+ #
186
+ # === Example:
187
+ #
188
+ # @post.errors => ["Foo can't be blank"]
189
+ #
190
+ def errors
191
+ []
192
+ end
193
+
194
+ # Returns YamlRecord Instance
195
+ # Complies with ActiveModel api
196
+ #
197
+ # === Example:
198
+ #
199
+ # @post.to_model => @post
200
+ #
201
+ def to_model
202
+ self
203
+ end
204
+
205
+ # Returns the instance of a record as a parameter
206
+ # By default return an id
207
+ #
208
+ # === Example:
209
+ #
210
+ # @post.to_param => <id>
211
+ #
212
+ def to_param
213
+ self.id
214
+ end
215
+
216
+ # Reload YamlRecord instance attributes from file
217
+ #
218
+ # === Example:
219
+ #
220
+ # @post = Post.create(:foo => "bar", :miso => "great")
221
+ # @post.foo = "bazz"
222
+ # @post.reload
223
+ # @post.foo => "bar"
224
+ #
225
+ def reload
226
+ record = self.class.find(self.id)
227
+ self.attributes = record.attributes
228
+ record
229
+ end
230
+
231
+ # Find YamlRecord instance given attribute name and expected value
232
+ # Supports checking inclusion for array based values
233
+ # Returns instance if found; false otherwise
234
+ #
235
+ # === Example:
236
+ #
237
+ # Post.find_by_attribute(:foo, "bar") => @post
238
+ # Post.find_by_attribute(:some_list, "item") => @post
239
+ #
240
+ def self.find_by_attribute(dynamic_source='default', attribute, expected_value)
241
+ self.all(dynamic_source).find do |record|
242
+ value = record.send(attribute) if record.respond_to?(attribute)
243
+ value.is_a?(Array) ?
244
+ value.include?(expected_value) :
245
+ value == expected_value
246
+ end
247
+ end
248
+
249
+ class << self;
250
+
251
+ # Find YamlRecord instance given id
252
+ # Returns instance if found; false otherwise
253
+ #
254
+ # === Example:
255
+ #
256
+ # Post.find_by_id("a1b2c3") => @post
257
+ #
258
+ def find_by_id(dynamic_source='default', value)
259
+ self.find_by_attribute(dynamic_source, :id, value)
260
+ end
261
+ alias :find :find_by_id
262
+ end
263
+
264
+ # Returns collection of all YamlRecord instances
265
+ # Caches results during request
266
+ #
267
+ # === Example:
268
+ #
269
+ # Post.all => [@post1, @post2, ...]
270
+ # Post.all(true) => (...force reload...)
271
+ #
272
+ def self.all(dynamic_source='default')
273
+ raw_items = self.adapter.read(self.source, dynamic_source, self.source_file_name) || []
274
+ raw_items.map { |item| self.new(item.merge(:persisted => true)) }
275
+ end
276
+
277
+ # Find last YamlRecord instance given a limit
278
+ # Returns an array of instances if found; empty otherwise
279
+ #
280
+ # === Example:
281
+ #
282
+ # Post.last => @post6
283
+ # Post.last(3) => [@p4, @p5, @p6]
284
+ #
285
+ def self.last(dynamic_source='default', limit=1)
286
+ limit == 1 ? self.all(dynamic_source).last : self.all(dynamic_source).last(limit)
287
+ end
288
+
289
+ # Find first YamlRecord instance given a limit
290
+ # Returns an array of instances if found; empty otherwise
291
+ #
292
+ # === Example:
293
+ #
294
+ # Post.first => @post
295
+ # Post.first(3) => [@p1, @p2, @p3]
296
+ #
297
+ def self.first(dynamic_source='default', limit=1)
298
+ limit == 1 ? self.all(dynamic_source).first : self.all(dynamic_source).first(limit)
299
+ end
300
+
301
+ # Initializes YamlRecord instance given an attribute hash and saves afterwards
302
+ # Returns instance if successfully saved; false otherwise
303
+ #
304
+ # === Example:
305
+ #
306
+ # Post.create(:foo => "bar", :miso => "great") => @post
307
+ #
308
+ def self.create(attributes={})
309
+ @fs = self.new(attributes)
310
+ if @fs.save == true
311
+ @fs.is_created = true;
312
+ @fs
313
+ else
314
+ false
315
+ end
316
+ end
317
+
318
+ # Declares persisted attributes for YamlRecord class
319
+ #
320
+ # === Example:
321
+ #
322
+ # class Post < YamlRecord::Base; properties :foo, :miso; end
323
+ # Post.create(:foo => "bar", :miso => "great") => @post
324
+ #
325
+ def self.properties(*names)
326
+ @_properties ||= []
327
+ if names.size == 0 # getter
328
+ @_properties
329
+ elsif names.size > 0 # setter
330
+ names = names | [:id]
331
+ setup_properties!(*names)
332
+ @_properties += names
333
+ end
334
+ end
335
+
336
+ # Declares or retrieves adapter for Yaml storage
337
+ # Returns an instance of an adapter
338
+ #
339
+ # === Example:
340
+ #
341
+ # class Post < YamlRecord::Base
342
+ # adapter :redis, @redis # => YamlRecord::Adapters::RedisAdapter
343
+ # end
344
+ #
345
+ def self.adapter(kind=nil, *options)
346
+ kind.nil? ? @_adapter_kind ||= :local : @_adapter_kind = kind
347
+ @_adapter ||= eval("YamlRecord::Adapters::#{@_adapter_kind.to_s.capitalize}Store").new(*options)
348
+ end
349
+
350
+ # Declares source file for YamlRecord class
351
+ #
352
+ # === Example:
353
+ #
354
+ # class Post < YamlRecord::Base
355
+ # source "path/to/yaml/file"
356
+ # end
357
+ #
358
+ def self.source(file=nil)
359
+ file ? @file = (file.to_s) : @file
360
+ end
361
+
362
+ # Declares source_file_name for YamlRecord class
363
+ #
364
+ # === Example:
365
+ #
366
+ # class Post < YamlRecord::Base
367
+ # source_file_name "file_name"
368
+ # end
369
+ #
370
+ def self.source_file_name(source_file_name=nil)
371
+ source_file_name ? @source_file_name = (source_file_name.to_s + ".yml") : @source_file_name
372
+ end
373
+
374
+ # Overrides equality to match if matching ids
375
+ #
376
+ def ==(comparison_record)
377
+ self.id == comparison_record.id
378
+ end
379
+
380
+ protected
381
+
382
+ # Validates each persisted attributes
383
+ # TODO Implement validation
384
+ #
385
+ def self.validates_each(*args, &block)
386
+ true
387
+ end
388
+
389
+ # Write raw yaml data to file
390
+ # Protected method, not called during usage
391
+ #
392
+ # === Example:
393
+ #
394
+ # Post.write_content([{ :foo => "bar"}, { :foo => "baz"}, ...]) # writes to source file
395
+ #
396
+ def self.write_contents(dynamic_source, raw_data)
397
+ self.adapter.write(self.source, self.source_file_name, dynamic_source, raw_data)
398
+ @records = nil
399
+ end
400
+
401
+ # Creates reader and writer methods for each persisted attribute
402
+ # Protected method, not called during usage
403
+ #
404
+ # === Example:
405
+ #
406
+ # Post.setup_properties!(:foo)
407
+ # @post.foo = "baz"
408
+ # @post.foo => "baz"
409
+ #
410
+ def self.setup_properties!(*names)
411
+ names.each do |name|
412
+ define_method(name) { self[name.to_sym] }
413
+ define_method("#{name}=") { |val| self[name.to_sym] = val }
414
+ end
415
+ end
416
+
417
+ # Assign YamlRecord a unique id if not set
418
+ # Invoke before create of an instance
419
+ # Protected method, not called during usage
420
+ #
421
+ def set_id!
422
+ self.id = SecureRandom.hex(15)
423
+ end
424
+ end
425
+ end
@@ -0,0 +1,3 @@
1
+ module YamlRecord
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_support/core_ext/kernel'
2
+ require 'active_support/core_ext/class'
3
+ require 'active_support/core_ext/hash'
4
+ require 'securerandom'
5
+ require 'active_support/callbacks'
6
+ require 'yaml'
7
+
8
+ module YamlRecord
9
+ require File.dirname(__FILE__) + "/yaml_record/base"
10
+ require File.dirname(__FILE__) + "/yaml_record/adapters/redis_store"
11
+ require File.dirname(__FILE__) + "/yaml_record/adapters/local_store"
12
+ end
@@ -0,0 +1,216 @@
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 self.adapter method" do
181
+ should("return local adapter") do
182
+ class YamlOtherObject < YamlRecord::Base; end
183
+ assert_kind_of YamlRecord::Adapters::LocalStore, YamlOtherObject.adapter
184
+ end
185
+ should("support explicit local adapter") do
186
+ YamlObject.adapter(:local)
187
+ assert_kind_of YamlRecord::Adapters::LocalStore, YamlObject.adapter
188
+ end
189
+ should("support changing adapter") do
190
+ class YamlRedisObject < YamlRecord::Base; end
191
+ @object = Object.new
192
+ YamlRedisObject.adapter(:redis, @object)
193
+ assert_kind_of YamlRecord::Adapters::RedisStore, YamlRedisObject.adapter
194
+ assert_equal @object, YamlRedisObject.adapter.client
195
+ class YamlOtherObject < YamlRecord::Base; end
196
+ assert_kind_of YamlRecord::Adapters::LocalStore, YamlOtherObject.adapter
197
+ end
198
+ should("not support invalid adapter") do
199
+ assert_raise(NameError) { class YamlFakeObject < YamlRecord::Base; adapter(:fake); end }
200
+ end
201
+ end
202
+
203
+ context "for set_id!" do
204
+ setup do
205
+ @fs_no_id = YamlObject.new(@attr)
206
+ @fs_with_id = YamlObject.create(@attr)
207
+ @id = @fs_with_id.id
208
+ @fs_with_id.update_attributes(:title => "Gomiso")
209
+ end
210
+ should("not have any id"){ assert_nil @fs_no_id.id }
211
+ should("have a id"){ assert @fs_with_id.id }
212
+ should("keep the same id"){ assert_equal @fs_with_id.id, @id }
213
+ end
214
+ end
215
+
216
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require File.dirname(__FILE__) + "/../lib/yaml_record"
5
+
6
+ class Test::Unit::TestCase
7
+ def clean_yaml_record(class_record)
8
+ File.open(class_record.source, 'w') {|f| f.write(nil) }
9
+ end
10
+
11
+ # Asserts that the condition is not true
12
+ # assert_false @title == "hey"
13
+ def assert_false(condition, message=nil)
14
+ assert !condition, message
15
+ end
16
+ end
@@ -0,0 +1,25 @@
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_record_rails_4"
7
+ s.version = YamlRecord::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Nico Taing", "Nathan Esquenazi", 'Ruslan Hamidullin', 'Eduard Gataullin']
10
+ s.email = ["edikgat@gmail.com"]
11
+ s.homepage = "https://github.com/edikgat/yaml_record/tree/rails4"
12
+ s.summary = %q{This gem is a fork to gem yaml_record with rails 4 support}
13
+ s.description = %q{Use YAML for persisted data with ActiveModel interface}
14
+
15
+ s.rubyforge_project = "yaml_record_rails_4"
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.0.0'
23
+ s.add_development_dependency 'rake', '~> 1.5.2'
24
+ s.add_development_dependency 'shoulda'
25
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yaml_record_rails_4
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nico Taing
8
+ - Nathan Esquenazi
9
+ - Ruslan Hamidullin
10
+ - Eduard Gataullin
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2013-09-20 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 4.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 4.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ~>
35
+ - !ruby/object:Gem::Version
36
+ version: 1.5.2
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.5.2
44
+ - !ruby/object:Gem::Dependency
45
+ name: shoulda
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ description: Use YAML for persisted data with ActiveModel interface
59
+ email:
60
+ - edikgat@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - Gemfile
67
+ - MIT-LICENSE
68
+ - README.md
69
+ - Rakefile
70
+ - lib/yaml_record/adapters/local_store.rb
71
+ - lib/yaml_record/adapters/redis_store.rb
72
+ - lib/yaml_record/base.rb
73
+ - lib/yaml_record/version.rb
74
+ - lib/yaml_record_rails_4.rb
75
+ - test/base_test.rb
76
+ - test/test_helper.rb
77
+ - yaml_record.gemspec
78
+ homepage: https://github.com/edikgat/yaml_record/tree/rails4
79
+ licenses: []
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project: yaml_record_rails_4
97
+ rubygems_version: 2.0.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: This gem is a fork to gem yaml_record with rails 4 support
101
+ test_files: []