simple_file_store 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ announcement.txt
2
+ coverage
3
+ doc
4
+ pkg
5
+ .raketasks
6
+ .rvmrc
7
+ README.html
8
+ README.pdf
9
+ *.lock
10
+ *.sw?
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/History.txt ADDED
@@ -0,0 +1,23 @@
1
+ == 0.0.4 / 2011-12-17
2
+ * Completely rehauled the gem infrastructure:
3
+ * Dropped Bones in favor of Bundler for gem management;
4
+ * Added Bundler for managing development/test dependencies;
5
+ * Completely rewritten the rake tasks, to make them simpler and more useful;
6
+ * Ensured the library is 1.9.2 compatible;
7
+ * Changed the base class from SimpleFileStore to SimpleFileStore::Base, which
8
+ allows us to use the module as a global namespace for project related code;
9
+ * Simplified tests;
10
+ * Various other cleanup.
11
+
12
+ == 0.0.3 / 2011-07-02
13
+ * Dropped using the BlankSlate gem in favor of a minimal, local lib;
14
+ * Introduced a features() class methods for easily adding store features.
15
+
16
+ == 0.0.2 / 2011-03-07
17
+ * Added CachingFileStore, cleaned up and simplified all existing Stores;
18
+ * Added automated/unit testing for everything.
19
+
20
+ == 0.0.1 / 2011-02-10
21
+
22
+ * 1 major enhancement:
23
+ * Birthday!
data/README.asciidoc ADDED
@@ -0,0 +1,86 @@
1
+ Simple File Store
2
+ =================
3
+ Alexandru Ungur <alexaandru@gmail.com>
4
+ :icons:
5
+ :toc:
6
+ :website: http://github.com/alexaandru/simple_file_store
7
+
8
+ Description
9
+ -----------
10
+
11
+ A micro-framework for storing and loading files to/from the filesystem.
12
+
13
+ Features/Problems
14
+ -----------------
15
+
16
+ * Simplicity! which makes it trivial to extend (see SecureFileStore or ScalableFileStore)
17
+ * Handles the details of file_name composition/decomposition in a DSLish way
18
+ * Handles file content storing/loading to/from file
19
+ * TODO: implement validations (file name components should not be blank, etc.)
20
+
21
+ Synopsis
22
+ --------
23
+
24
+ .Example Usage
25
+ --------------------------------------------------------------------
26
+ class MyUpload < SimpleFileStore::Base
27
+
28
+ features :scalable, :encrypted
29
+
30
+ file_name_tokens :user_id, :category_id, :timestamp
31
+
32
+ end
33
+
34
+ params = {:content => "IO or string", :user_id => 1, :category_id => 17}
35
+ m = MyUpload.new(params) # file content is saved to fstore/my_uploads/29/b7/f2/25/1-17-1324284042.txt
36
+ m.file_name # => "1-17-1324284042.txt"
37
+
38
+ to load, simply call:
39
+ m = MyUpload.new(:file_name => "1-17-1324284042.txt")
40
+ m.content # => "file content"
41
+ --------------------------------------------------------------------
42
+
43
+ Requirements
44
+ ------------
45
+
46
+ * ActiveSupport (for Inflections);
47
+ * Encryption Store needs an encryption library;
48
+
49
+ Install
50
+ -------
51
+
52
+ ----------------------------------
53
+ gem install simple_file_store
54
+
55
+ or add this to Gemfile:
56
+
57
+ gem 'simple_file_store'
58
+ ----------------------------------
59
+
60
+ License
61
+ -------
62
+
63
+ (The MIT License)
64
+
65
+ Copyright (c) 2011 Alexandru Ungur
66
+
67
+ Permission is hereby granted, free of charge, to any person obtaining
68
+ a copy of this software and associated documentation files (the
69
+ 'Software'), to deal in the Software without restriction, including
70
+ without limitation the rights to use, copy, modify, merge, publish,
71
+ distribute, sublicense, and/or sell copies of the Software, and to
72
+ permit persons to whom the Software is furnished to do so, subject to
73
+ the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be
76
+ included in all copies or substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
79
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
80
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
81
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
82
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
83
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
84
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85
+
86
+ // vim: set syntax=asciidoc:
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ def root
4
+ Pathname.new(File.dirname(__FILE__))
5
+ end
6
+
7
+ def test_files
8
+ Dir[root.join('test', '*_test.rb')].map{|x| x.inspect}.join(' ')
9
+ end
10
+
11
+ namespace :test do
12
+ desc "Check code quality"
13
+ task :quality do
14
+ %w|roodi flog flay|.each do |e|
15
+ puts "=== #{e.capitalize} #{'=' * 80}"
16
+ sh "#{e} lib/**/*.rb"
17
+ puts
18
+ end
19
+ end
20
+
21
+ namespace :unit do
22
+ test_files.split(' ').each do |tt|
23
+ name = File.basename(tt, '.rb"')[0..-6]
24
+ desc "Run #{name} unit test"
25
+ task name do
26
+ sh "ruby #{root.join('test', 'loader.rb')} #{tt}"
27
+ end
28
+ end
29
+ end
30
+
31
+ desc "List the tests spec"
32
+ task :spec do
33
+ require 'test/unit'
34
+ test_files.delete('"').split(' ').sort.each do |file|
35
+ out = Test::Unit::TestCase.new(nil).capture_io { load file }.join
36
+ klass = File.basename(file, '.rb').classify
37
+
38
+ unless Object.const_defined?(klass.to_s)
39
+ puts "Skipping #{klass} because it doesn't map to a Class"
40
+ next
41
+ end
42
+
43
+ klass = klass.constantize
44
+ name = klass.name.gsub('Test', '')
45
+ puts name, '-' * name.size
46
+ test_methods = klass.instance_methods.grep(/^test/).map {|s| s.to_s.gsub(/^test: /, '')}.sort
47
+ test_methods.each { |m| puts " " << m }
48
+ puts out.nil? || out.empty? ? nil : [out, nil]
49
+ end
50
+
51
+ exit 1 # to skip running tests suite
52
+ end
53
+ end
54
+
55
+ desc "Run all tests"
56
+ task :test do
57
+ sh "ruby #{root.join('test', 'loader.rb')} #{test_files}"
58
+ end
59
+
60
+ namespace :test do
61
+ task :prepare_coverage do
62
+ ENV['COVERAGE'] = '1'
63
+ end
64
+
65
+ desc "Run tests coverage report"
66
+ task :coverage => [:prepare_coverage, :test]
67
+ end
68
+
69
+ task :default => :test
@@ -0,0 +1,13 @@
1
+ #
2
+ # Credit goes to Jim Weirich for the BlankSlate technique,
3
+ # adapted from his example at: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
4
+ #
5
+ class MiniBlankSlate
6
+
7
+ BlankMethods = /^__|^instance_|object_id$|class$|inspect$|respond_to\?$/.freeze
8
+
9
+ instance_methods.each do |m|
10
+ m =~ BlankMethods or undef_method(m)
11
+ end
12
+
13
+ end
@@ -0,0 +1,7 @@
1
+ # Load BlankSlate
2
+ require File.join(File.dirname(__FILE__), 'mini_blank_slate')
3
+
4
+ # Load all SimpleFileStore libs
5
+ Dir[File.join(File.dirname(__FILE__), 'simple_file_store', '*.rb')].each do |lib|
6
+ require lib
7
+ end
@@ -0,0 +1,30 @@
1
+ #
2
+ # This adds a transparent file caching layer.
3
+ #
4
+ # Behavior: when neither file_name nor content are
5
+ # provided, the generate_content() method is called
6
+ # and the output loaded into file (and thus, the
7
+ # file autosaved).
8
+ #
9
+ # Requirements: a generate_content() method.
10
+ #
11
+ # TODO: Implement an easy way to select the caching method
12
+ # vs. use the current convention.
13
+ # TODO: imlpement some hooks in base class, so that redefining
14
+ # methods should not be necessary.
15
+ #
16
+
17
+ module CachingFileStore
18
+
19
+ protected
20
+
21
+ def load_or_store!
22
+ self.content = generate_content unless File.exist?(path)
23
+ open {|f| content.nil? ? pull(f) : push(f)}
24
+ end
25
+
26
+ def generate_content
27
+ raise NoMethodError, "You must implement the method yourself"
28
+ end
29
+
30
+ end
@@ -0,0 +1,29 @@
1
+ #
2
+ # This adds transparent file path scalability (by using
3
+ # 4 depth trees hierarchy based on the file name).
4
+ #
5
+ # This allows one to have as many files (as the underlying
6
+ # filesystem allows) without every having to worry about
7
+ # too many files under one folder.
8
+ #
9
+
10
+ require 'digest'
11
+ module ScalableFileStore
12
+
13
+ def path
14
+ scale_path
15
+ root.join(self.class.name.tableize, @inter_tree, file_name)
16
+ end
17
+
18
+ private
19
+
20
+ def scale_path
21
+ raise ArgumentError, "Cowardly refusing to scale empty filename" if file_name.nil?
22
+
23
+ @inter_tree ||= begin
24
+ h = Digest::SHA256.hexdigest(file_name)
25
+ File.join(*[h[0,2],h[2,2],h[4,2],h[6,2]].reject{|z| z.nil? || z.empty?})
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # This adds transparent file content encryption/decryption
3
+ # on top of SimpleFileStore
4
+ #
5
+ # TODO: Implement an easy way to select the encryptor/decryptor.
6
+ #
7
+
8
+ module SecureFileStore
9
+
10
+ private
11
+
12
+ def pull(f)
13
+ raw = f.read
14
+ self.content = App.decrypt(raw) || raw
15
+ end
16
+
17
+ def push(f)
18
+ f.write(App.encrypt(content))
19
+ end
20
+
21
+ end
@@ -0,0 +1,158 @@
1
+ #
2
+ # SimpleFileStore implements a micro-framework for mundane file
3
+ # operations such as storing or loading a file.
4
+ #
5
+ # At it's core it does one of two things:
6
+ #
7
+ # * auto loads a file (if :file_name => <file_name> is passed)
8
+ # * auto stores a file (if :content => <content> is passed)
9
+ #
10
+ # To put it simple, if you give it some content, it will store it
11
+ # and give you a file name. If you give it a file name it will load
12
+ # the file content.
13
+ #
14
+ # This class is not intended to perform any other work, than just
15
+ # this basic one. Any extra functionality will be layered on top
16
+ # of it (see for example ScalableFileStore or SecureFileStore).
17
+ #
18
+ # It also offers a very handy way of composing/deomposing file names:
19
+ # you simply indicate the tokens that should compose the filename,
20
+ # and it will auto construct the file name (on content auto saving),
21
+ # respectiely it will decompose the file name into tokens when auto
22
+ # loading date from file.
23
+ #
24
+
25
+ #
26
+ # TODO: implement further validations (file name components should
27
+ # not be blank, etc.);
28
+ # TODO: make FileStoreRoot and Separator configurable.
29
+ #
30
+ require 'pathname'
31
+ require 'fileutils'
32
+ require 'rubygems'
33
+ require 'active_support/inflector'
34
+
35
+ module SimpleFileStore
36
+
37
+ class Base < MiniBlankSlate
38
+
39
+ KnownFeatures = Dir.glob(File.join(File.dirname(__FILE__), '*_file_store.rb')).map do |f|
40
+ f =~ %r|.*/([a-z]+)_file_store.rb$|
41
+ $1 && $1 != 'simple' ? $1 : nil
42
+ end.compact.freeze
43
+ FileStoreRoot = "fstore".freeze
44
+ Separator = "-".freeze
45
+
46
+ attr_accessor :root, :content_type, :timestamp, :usec
47
+ attr :content
48
+ attr :file_name
49
+
50
+ class << self
51
+ def file_name_tokens(*args)
52
+ @file_name_tokens ||= []
53
+ unless args.empty?
54
+ @file_name_tokens.concat(args)
55
+ args.each do |token|
56
+ next if [:timestamp, :usec].include?(token.to_sym)
57
+ instance_eval { attr_accessor token }
58
+ end
59
+ end
60
+ @file_name_tokens
61
+ end
62
+
63
+ def features(*args)
64
+ args = args.flatten.compact
65
+ raise ArgumentError, "Unknown feature" unless args.all?{|a| KnownFeatures.include?(a.to_s)}
66
+
67
+ args.each do |a|
68
+ instance_eval { include "#{a}_file_store".classify.constantize }
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize(args = {})
74
+ init_root!
75
+ validate!
76
+ load_arguments(args)
77
+ load_or_store!
78
+ end
79
+
80
+ def path
81
+ root.join(self.class.name.tableize, file_name)
82
+ end
83
+
84
+ def file_name=(new_name)
85
+ return false if new_name.nil? || new_name.empty?
86
+
87
+ if new_name =~ /^(.*)\.(.*?)$/
88
+ @file_name, self.content_type = new_name, $2
89
+ file_name_tokens.zip($1.split(Separator)) {|(k, v)| __send__("#{k}=", v)}
90
+ else
91
+ raise ArgumentError, "Unrecognized file name: #{new_name}"
92
+ end
93
+ end
94
+
95
+ def content=(new_content)
96
+ return false if new_content.nil?
97
+
98
+ @content = if new_content.respond_to?(:read) then new_content.read
99
+ elsif new_content.respond_to?(:to_s) then new_content.to_s
100
+ else new_content
101
+ end
102
+ end
103
+
104
+ def open(flag = 'r+', &block)
105
+ path.dirname.mkpath
106
+ FileUtils.touch(path)
107
+ File.open(path, flag, &block)
108
+ end
109
+
110
+ protected
111
+
112
+ def load_or_store!
113
+ open {|f| content.nil? ? pull(f) : push(f)}
114
+ end
115
+
116
+ def pull(f)
117
+ self.content = f.read
118
+ end
119
+
120
+ def push(f)
121
+ f.write(content)
122
+ end
123
+
124
+ private
125
+
126
+ def init_root!
127
+ self.root ||= case
128
+ when defined?(Rails) then Rails.root
129
+ when defined?(AppRoot) then AppRoot.is_a?(Pathname) ? AppRoot : Pathname.new(AppRoot.to_s)
130
+ else Pathname.new(FileUtils.pwd)
131
+ end.join(FileStoreRoot)
132
+ end
133
+
134
+ def validate!
135
+ raise Errno::ENOENT, "Root not found: #{root}!" unless root.exist?
136
+ end
137
+
138
+ def load_arguments(args)
139
+ # passed (as-is) args
140
+ args = {:content_type => 'csv'}.merge(args)
141
+ args.each {|(k,v)| __send__ "#{k}=", v}
142
+
143
+ # custom (builtin) args
144
+ t = Time.now
145
+ self.timestamp = t.to_i
146
+ self.usec = t.usec
147
+
148
+ # hybrid args (depending on passed+builtin)
149
+ @file_name ||= file_name_tokens.map{|k| __send__(k)}.join(Separator) << '.' << content_type
150
+ end
151
+
152
+ def file_name_tokens
153
+ self.class.file_name_tokens
154
+ end
155
+
156
+ end
157
+
158
+ end
@@ -0,0 +1,5 @@
1
+ module SimpleFileStore
2
+
3
+ VERSION = '0.0.4'
4
+
5
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'simple_file_store/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'simple_file_store'
7
+ s.version = SimpleFileStore::VERSION
8
+ s.authors = ['Alex Ungur']
9
+ s.date = '2011-12-17'
10
+ s.email = ['alexaandru@gmail.com']
11
+ s.homepage = %q|http://rubygems.org/gems/simple_file_store|
12
+ s.summary = %q|A file storing/loading micro-framework.|
13
+ s.description = %q|A micro-framework for automating storing and loading files to/from the filesystem.|
14
+
15
+ s.rubyforge_project = 'simple_file_store'
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
+ s.rdoc_options = ['--main', 'README.asciidoc']
22
+
23
+ # specify any dependencies here; for example:
24
+ s.add_runtime_dependency 'activesupport', ['~>2.3.0']
25
+ s.add_development_dependency 'rake'
26
+ s.add_development_dependency 'bundler'
27
+ s.add_development_dependency 'shoulda'
28
+ s.add_development_dependency 'simplecov'
29
+ s.add_development_dependency 'roodi'
30
+ s.add_development_dependency 'flay'
31
+ s.add_development_dependency 'flog'
32
+ end
data/test/base.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'test/unit'
2
+ require 'fileutils'
3
+
4
+ require 'rubygems'
5
+ require 'shoulda'
6
+ if RUBY_VERSION >= '1.9.0'
7
+ require 'simplecov'
8
+ SimpleCov.start do
9
+ add_filter "/test/"
10
+ add_group "Libraries", "/lib/"
11
+ command_name "Combined Tests"
12
+ end if ENV['COVERAGE']
13
+ end
14
+
15
+ require './lib/simple_file_store'
16
+ # The testing fstore root
17
+ AppRoot = File.dirname(__FILE__)
18
+
19
+ module SimpleFileStore
20
+
21
+ class TestCase < Test::Unit::TestCase
22
+
23
+ def assert_dir_content_difference(dir, depth = ['*'], difference = 1)
24
+ raise ArgumentError, "Directory not found" unless File.exist?(dir)
25
+ before_content = Dir[File.join(dir, *depth)]
26
+ yield
27
+ after_content = Dir[File.join(dir, *depth)]
28
+ assert_equal before_content.size + difference, after_content.size,
29
+ "Expected an increase of #{difference} in '#{dir}' folder contents, got a difference of #{after_content.size - before_content.size}"
30
+ after_content - before_content
31
+ end
32
+
33
+ def assert_no_dir_content_difference(dir, depth = ['*'], &block)
34
+ assert_dir_content_difference(dir, depth, 0, &block)
35
+ end
36
+
37
+ def assert_attributes_set(ts, file_content, record_id, expected_file_name)
38
+ assert_equal file_content, ts.content, "Should set file/data"
39
+ assert_equal File.basename(expected_file_name), ts.file_name, "Should set file_name"
40
+ assert_equal record_id, ts.record_id, "Should set record ID"
41
+ assert ts.timestamp, "Should set timestamp"
42
+ assert ts.usec, "Should set timestamp/.usec"
43
+ assert_equal 'csv', ts.content_type, "Should set content_type"
44
+ assert_match %r|/test/fstore$|, ts.root.to_s, "Should set root"
45
+ assert_match %r|#{expected_file_name}$|, ts.path.to_s, "Should set path"
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,85 @@
1
+ require './test/base'
2
+
3
+ class TestCachingStore < SimpleFileStore::Base
4
+ features :caching
5
+
6
+ file_name_tokens :record_id, :alt_rec_id
7
+
8
+ def generate_content
9
+ "auto generated content %s/%s" % [record_id, alt_rec_id]
10
+ end
11
+ end
12
+
13
+ class TestBadCachingStore < SimpleFileStore::Base
14
+ include CachingFileStore
15
+
16
+ file_name_tokens :res_id, :alt_rec_id
17
+ end
18
+
19
+ class CachingFileStoreTest < SimpleFileStore::TestCase
20
+ context "Auto-saving to CachingFileStore" do
21
+ setup do
22
+ @record_id = "102"
23
+ @alt_rec_id = "foobar"
24
+ @file_content = "auto generated content 102/foobar"
25
+ @file_names = assert_dir_content_difference('test/fstore/test_caching_stores') {
26
+ @ts = TestCachingStore.new(:record_id => @record_id, :alt_rec_id => @alt_rec_id)
27
+ }
28
+ @expected_file_name = "test/fstore/test_caching_stores/#{@record_id}-#{@alt_rec_id}.csv"
29
+ end
30
+
31
+ teardown do
32
+ @file_names.each {|f| File.unlink(f)} unless @file_names.nil?
33
+ end
34
+
35
+ should "save content to store" do
36
+ assert File.exist?(@expected_file_name)
37
+ assert_equal @expected_file_name, @file_names.first
38
+ end
39
+
40
+ should "set attributes" do
41
+ assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
42
+ end
43
+
44
+ should "raise an error if generate_content is not defined" do
45
+ assert_raise NoMethodError do
46
+ TestBadCachingStore.new(:record_id => @record_id, :alt_rec_id => @alt_rec_id)
47
+ end
48
+ end
49
+ end
50
+
51
+ context "Auto-loading from CachingFileStore" do
52
+ setup do
53
+ @record_id = "1"
54
+ @alt_rec_id = "2"
55
+ @file_content = "xyz"
56
+ @file_name = "#{@record_id}#{TestCachingStore::Separator}#{@alt_rec_id}.csv"
57
+ @file_path = File.join('test', 'fstore', 'test_caching_stores', @file_name)
58
+ FileUtils.mkdir_p(File.dirname(@file_path))
59
+ File.open(@file_path, 'w') {|f| f.write @file_content}
60
+ @file_names = assert_no_dir_content_difference('test/fstore/test_caching_stores') {
61
+ @ts = TestCachingStore.new(:file_name => @file_name)
62
+ }
63
+ @expected_file_name = "test/fstore/test_caching_stores/#{@record_id}#{TestCachingStore::Separator}#{@alt_rec_id}.csv"
64
+ end
65
+
66
+ teardown do
67
+ File.unlink(@file_path)
68
+ end
69
+
70
+ should "load content from store" do
71
+ assert_equal @file_content, @ts.content
72
+ end
73
+
74
+ should "set attributes" do
75
+ assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ def assert_attributes_set(ts, file_content, record_id, alt_rec_id, expected_file_name)
82
+ super(ts, file_content, record_id, expected_file_name)
83
+ assert_equal alt_rec_id, ts.alt_rec_id, "Should set record ID"
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ require './test/base'
2
+
3
+ class CombinedFileStoreTest < SimpleFileStore::TestCase
4
+ should "test all combinations of stores"
5
+ end
File without changes
File without changes
File without changes
File without changes
data/test/loader.rb ADDED
@@ -0,0 +1 @@
1
+ ARGV.each {|f| load f unless f =~ /^-/}
@@ -0,0 +1,77 @@
1
+ require './test/base'
2
+
3
+ class TestScalableStore < SimpleFileStore::Base
4
+ features :scalable
5
+
6
+ file_name_tokens :record_id, :alt_rec_id
7
+ end
8
+
9
+ class ScalableFileStoreTest < SimpleFileStore::TestCase
10
+ context "Auto-saving to ScalableFileStore" do
11
+ setup do
12
+ @record_id = "101"
13
+ @alt_rec_id = "2345678"
14
+ @file_content = "xyz"
15
+ @file_names = assert_dir_content_difference('test/fstore/test_scalable_stores', %w|* * * * **|) {
16
+ @ts = TestScalableStore.new(:content => @file_content, :record_id => @record_id, :alt_rec_id => @alt_rec_id)
17
+ }
18
+ @expected_file_name = "test/fstore/test_scalable_stores/bb/37/22/88/#{@record_id}-#{@alt_rec_id}.csv"
19
+ end
20
+
21
+ teardown do
22
+ @file_names.each do |f|
23
+ path = File.dirname(File.dirname(File.dirname(File.dirname(f))))
24
+ raise ArgumentError, "You may be 'rm -rf'-ing the wrong path: '#{path}'. Aborting!" unless \
25
+ path =~ %r|test/fstore/test_scalable_stores/.*|
26
+ FileUtils.rm_rf(path)
27
+ end if @file_names
28
+ end
29
+
30
+ should "save content to store" do
31
+ assert File.exist?(@expected_file_name)
32
+ assert_equal @expected_file_name, @file_names.first
33
+ end
34
+
35
+ should "set attributes" do
36
+ assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
37
+ end
38
+ end
39
+
40
+ context "Auto-loading from ScalableFileStore" do
41
+ setup do
42
+ @record_id = "1"
43
+ @alt_rec_id = "2"
44
+ @file_content = "xyz"
45
+ @file_name = "#{@record_id}#{TestScalableStore::Separator}#{@alt_rec_id}.csv"
46
+ @file_path = File.join('test', 'fstore', 'test_scalable_stores', '96', 'c2', 'c5', 'c0', @file_name)
47
+ FileUtils.mkdir_p(File.dirname(@file_path))
48
+ File.open(@file_path, 'w') {|f| f.write @file_content}
49
+ @file_names = assert_no_dir_content_difference('test/fstore/test_scalable_stores', %w|* * * * **|) {
50
+ @ts = TestScalableStore.new(:file_name => @file_name)
51
+ }
52
+ @expected_file_name = "test/fstore/test_scalable_stores/96/c2/c5/c0/#{@record_id}#{TestScalableStore::Separator}#{@alt_rec_id}.csv"
53
+ end
54
+
55
+ teardown do
56
+ path = File.dirname(File.dirname(File.dirname(File.dirname(@file_path))))
57
+ raise ArgumentError, "You may be 'rm -rf'-ing the wrong path: '#{path}'. Aborting!" unless \
58
+ path =~ %r|test/fstore/test_scalable_stores/.*|
59
+ FileUtils.rm_rf(path)
60
+ end
61
+
62
+ should "load content from store" do
63
+ assert_equal @file_content, @ts.content
64
+ end
65
+
66
+ should "set attributes" do
67
+ assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ def assert_attributes_set(ts, file_content, record_id, alt_rec_id, expected_file_name)
74
+ super(ts, file_content, record_id, expected_file_name)
75
+ assert_equal alt_rec_id, ts.alt_rec_id, "Should set record ID"
76
+ end
77
+ end
@@ -0,0 +1,81 @@
1
+ require './test/base'
2
+
3
+ # This is pretending to encrypt something.
4
+ module App
5
+ extend self
6
+
7
+ def rot13(str)
8
+ str.tr "A-Za-z", "N-ZA-Mn-za-m"
9
+ end
10
+
11
+ alias :encrypt :rot13
12
+ alias :decrypt :rot13
13
+ end
14
+
15
+ class TestSecureStore < SimpleFileStore::Base
16
+ features :secure
17
+
18
+ file_name_tokens :record_id, :alt_rec_id
19
+ end
20
+
21
+ class SecureFileStoreTest < SimpleFileStore::TestCase
22
+ context "Auto-saving to SecureFileStore" do
23
+ setup do
24
+ @record_id = "102"
25
+ @alt_rec_id = "foobar"
26
+ @file_content = "foo bar baz"
27
+ @file_names = assert_dir_content_difference('test/fstore/test_secure_stores') {
28
+ @ts = TestSecureStore.new(:content => @file_content, :record_id => @record_id, :alt_rec_id => @alt_rec_id)
29
+ }
30
+ @expected_file_name = "test/fstore/test_secure_stores/#{@record_id}-#{@alt_rec_id}.csv"
31
+ end
32
+
33
+ teardown do
34
+ @file_names.each {|f| File.unlink(f)} unless @file_names.nil?
35
+ end
36
+
37
+ should "save content to store" do
38
+ assert File.exist?(@expected_file_name)
39
+ assert_equal @expected_file_name, @file_names.first
40
+ end
41
+
42
+ should "set attributes" do
43
+ assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
44
+ end
45
+ end
46
+
47
+ context "Auto-loading from SecureFileStore" do
48
+ setup do
49
+ @record_id = "1"
50
+ @alt_rec_id = "2"
51
+ @file_content = "xyz"
52
+ @file_name = "#{@record_id}#{TestSecureStore::Separator}#{@alt_rec_id}.csv"
53
+ @file_path = File.join('test', 'fstore', 'test_secure_stores', @file_name)
54
+ FileUtils.mkdir_p(File.dirname(@file_path))
55
+ File.open(@file_path, 'w') {|f| f.write @file_content}
56
+ @file_names = assert_no_dir_content_difference('test/fstore/test_secure_stores') {
57
+ @ts = TestSecureStore.new(:file_name => @file_name)
58
+ }
59
+ @expected_file_name = "test/fstore/test_secure_stores/#{@record_id}#{TestSecureStore::Separator}#{@alt_rec_id}.csv"
60
+ end
61
+
62
+ teardown do
63
+ File.unlink(@file_path)
64
+ end
65
+
66
+ should "load content from store" do
67
+ assert_equal App.decrypt(@file_content), @ts.content
68
+ end
69
+
70
+ should "set attributes" do
71
+ assert_attributes_set(@ts, App.decrypt(@file_content), @record_id, @alt_rec_id, @expected_file_name)
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ def assert_attributes_set(ts, file_content, record_id, alt_rec_id, expected_file_name)
78
+ super(ts, file_content, record_id, expected_file_name)
79
+ assert_equal alt_rec_id, ts.alt_rec_id, "Should set record ID"
80
+ end
81
+ end
@@ -0,0 +1,57 @@
1
+ require './test/base'
2
+
3
+ class TestStore < SimpleFileStore::Base
4
+ file_name_tokens :record_id, :timestamp, :usec
5
+ end
6
+
7
+ class SimpleFileStoreTest < SimpleFileStore::TestCase
8
+ context "Auto-saving to SimpleFileStore" do
9
+ setup do
10
+ @record_id = "100"
11
+ @file_content = "abc"
12
+ @file_names = assert_dir_content_difference('test/fstore/test_stores') {
13
+ @ts = TestStore.new(:content => @file_content, :record_id => @record_id)
14
+ @expected_file_name = File.join('test', TestStore::FileStoreRoot, 'test_stores',
15
+ "#{@ts.record_id}#{TestStore::Separator}#{@ts.timestamp}-#{@ts.usec}.#{@ts.content_type}")
16
+ }
17
+ end
18
+
19
+ teardown do
20
+ @file_names.each {|f| File.unlink(f)} unless @file_names.nil?
21
+ end
22
+
23
+ should "save content to store" do
24
+ assert File.exist?(@expected_file_name), "File '#{@expected_file_name}' should have been created!"
25
+ assert_equal @expected_file_name, @file_names.first
26
+ end
27
+
28
+ should "set attributes" do
29
+ assert_attributes_set(@ts, @file_content, @record_id, @expected_file_name)
30
+ end
31
+ end
32
+
33
+ context "Auto-loading from SimpleFileStore" do
34
+ setup do
35
+ @record_id = "101"
36
+ @file_content = "cde"
37
+ @file_name = "101-#{Time.now.to_i}-1.csv"
38
+ @file_path = File.join('test', 'fstore', 'test_stores', @file_name)
39
+ File.open(@file_path, 'w') {|f| f.write @file_content}
40
+ @file_names = assert_no_dir_content_difference('test/fstore/test_stores') {
41
+ @ts = TestStore.new(:file_name => @file_name)
42
+ }
43
+ end
44
+
45
+ teardown do
46
+ FileUtils.rm_f(@file_path)
47
+ end
48
+
49
+ should "load content from store" do
50
+ assert_equal @file_content, @ts.content
51
+ end
52
+
53
+ should "set attributes" do
54
+ assert_attributes_set(@ts, @file_content, @record_id, @file_path)
55
+ end
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,205 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_file_store
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ version: 0.0.4
11
+ platform: ruby
12
+ authors:
13
+ - Alex Ungur
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-17 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 0
34
+ version: 2.3.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rake
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: bundler
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: shoulda
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ name: simplecov
81
+ prerelease: false
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ type: :development
92
+ version_requirements: *id005
93
+ - !ruby/object:Gem::Dependency
94
+ name: roodi
95
+ prerelease: false
96
+ requirement: &id006 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ type: :development
106
+ version_requirements: *id006
107
+ - !ruby/object:Gem::Dependency
108
+ name: flay
109
+ prerelease: false
110
+ requirement: &id007 !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 3
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ type: :development
120
+ version_requirements: *id007
121
+ - !ruby/object:Gem::Dependency
122
+ name: flog
123
+ prerelease: false
124
+ requirement: &id008 !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ hash: 3
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ type: :development
134
+ version_requirements: *id008
135
+ description: A micro-framework for automating storing and loading files to/from the filesystem.
136
+ email:
137
+ - alexaandru@gmail.com
138
+ executables: []
139
+
140
+ extensions: []
141
+
142
+ extra_rdoc_files: []
143
+
144
+ files:
145
+ - .gitignore
146
+ - Gemfile
147
+ - History.txt
148
+ - README.asciidoc
149
+ - Rakefile
150
+ - lib/mini_blank_slate.rb
151
+ - lib/simple_file_store.rb
152
+ - lib/simple_file_store/caching_file_store.rb
153
+ - lib/simple_file_store/scalable_file_store.rb
154
+ - lib/simple_file_store/secure_file_store.rb
155
+ - lib/simple_file_store/simple_file_store.rb
156
+ - lib/simple_file_store/version.rb
157
+ - simple_file_store.gemspec
158
+ - test/base.rb
159
+ - test/caching_file_store_test.rb
160
+ - test/combined_file_store_test.rb
161
+ - test/fstore/test_caching_stores/.keep
162
+ - test/fstore/test_scalable_stores/.keep
163
+ - test/fstore/test_secure_stores/.keep
164
+ - test/fstore/test_stores/.keep
165
+ - test/loader.rb
166
+ - test/scalable_file_store_test.rb
167
+ - test/secure_file_store_test.rb
168
+ - test/simple_file_store_test.rb
169
+ has_rdoc: true
170
+ homepage: http://rubygems.org/gems/simple_file_store
171
+ licenses: []
172
+
173
+ post_install_message:
174
+ rdoc_options:
175
+ - --main
176
+ - README.asciidoc
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ hash: 3
185
+ segments:
186
+ - 0
187
+ version: "0"
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ none: false
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ hash: 3
194
+ segments:
195
+ - 0
196
+ version: "0"
197
+ requirements: []
198
+
199
+ rubyforge_project: simple_file_store
200
+ rubygems_version: 1.5.2
201
+ signing_key:
202
+ specification_version: 3
203
+ summary: A file storing/loading micro-framework.
204
+ test_files: []
205
+