simple_file_store 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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
+