active_model_archive 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format nested
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in active_model_archive.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "active_model_archive/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "active_model_archive"
7
+ s.version = ActiveModelArchive::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Grant Rodgers"]
10
+ s.email = ["grantr@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Dump and restore activemodel objects}
13
+ s.description = %q{Adds dump and restore functionality to activemodel classes}
14
+
15
+ s.rubyforge_project = "active_model_archive"
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.add_dependency('activesupport', '~> 3')
22
+ s.add_dependency('yajl-ruby', '>= 0')
23
+ s.add_development_dependency('rspec', '~> 2')
24
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveModelArchive
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,166 @@
1
+ require 'zlib'
2
+ require 'active_support/concern'
3
+
4
+ module ActiveModel
5
+ module Archive
6
+ extend ActiveSupport::Concern
7
+
8
+ MAGIC = 42098
9
+ CURRENT_VERSION = 1
10
+ SUPPORTED_VERSIONS = [1]
11
+
12
+ # dump file format:
13
+ # version 1:
14
+ #
15
+ # magic 4 bytes
16
+ # version 4 bytes
17
+ #
18
+ # record length 4 bytes
19
+ # record + \n
20
+ # crc 4 bytes
21
+ # record length 4 bytes
22
+ # record + \n
23
+ # crc 4 bytes
24
+ # ...
25
+ #
26
+ # null 4 bytes
27
+ # record count 4 bytes
28
+ # magic 4 bytes
29
+ #
30
+
31
+ module ClassMethods
32
+ # Dump records to a stream.
33
+ # uses find_each without query params
34
+ # uses paginated_each with query params
35
+ # executes the block with each dumped record if given
36
+ # the block can be used to log progress
37
+ def dump!(stream, query_params={}, &block)
38
+ # set the encoding so we can write packed ints
39
+ stream.set_encoding("ASCII-8BIT")
40
+
41
+ archive_write_int(stream, MAGIC)
42
+ archive_write_int(stream, CURRENT_VERSION)
43
+
44
+ count = 0
45
+ encoder = Yajl::Encoder.new
46
+
47
+ writer = lambda do |json|
48
+ # write size, record, and crc
49
+ archive_write_int(stream, json.bytesize)
50
+ stream.write(json)
51
+ archive_write_int(stream, Zlib::crc32(json))
52
+ count += 1
53
+ end
54
+
55
+ if query_params.blank?
56
+ find_each do |object|
57
+ encoder.encode(object.as_archive, nil, &writer)
58
+ block.call(object) if block_given?
59
+ end
60
+ else
61
+ paginated_each(query_params) do |object|
62
+ encoder.encode(object.as_archive, nil, &writer)
63
+ block.call(object) if block_given?
64
+ end
65
+ end
66
+
67
+ archive_write_int(stream, 0)
68
+ archive_write_int(stream, count)
69
+ archive_write_int(stream, MAGIC)
70
+
71
+ stream.flush
72
+ return count
73
+ end
74
+
75
+ # Restore records from a stream.
76
+ # require attributes= and save accepting :validate option
77
+ # set :check true to only check the archive and not restore. if no exceptions are thrown, archive is valid.
78
+ # set :validate true to validate records as they are saved
79
+ # executes the block with each restored record if given
80
+ # the block can be used to log progress and warn about invalid objects
81
+ def restore!(stream, options={}, &block)
82
+ # check magic byte
83
+ magic = archive_read_int(stream)
84
+ raise "Not an archive stream" unless magic == MAGIC
85
+
86
+ # check version byte
87
+ version = archive_read_int(stream)
88
+ raise "Unsupported version #{version}" unless SUPPORTED_VERSIONS.include?(version)
89
+
90
+ count = 0
91
+ parser = Yajl::Parser.new
92
+
93
+ if options[:check]
94
+ # if we are just checking, increment but do not create anything
95
+ parser.on_parse_complete = lambda do |hash|
96
+ count += 1
97
+ end
98
+ else
99
+ # for each hash read, create an object
100
+ parser.on_parse_complete = lambda do |hash|
101
+ o = from_archive(hash)
102
+ o.save(:validate => !!options[:validate])
103
+ block.call(o) if block_given?
104
+ count += 1
105
+ end
106
+ end
107
+
108
+ # loop through records until a null is read
109
+ record_size = archive_read_int(stream)
110
+ while record_size != 0
111
+ json = stream.read(record_size)
112
+ crc = archive_read_int(stream)
113
+ raise "Invalid CRC at #{stream.pos-1}" unless crc == Zlib::crc32(json)
114
+ parser << json
115
+ record_size = archive_read_int(stream)
116
+ end
117
+
118
+ record_count = archive_read_int(stream)
119
+ magic = archive_read_int(stream)
120
+ raise "Unexpected end of archive file" unless magic == MAGIC
121
+ raise "Record counts didn't match: #{record_count} in stream, #{count} read" unless record_count == count
122
+
123
+ return count
124
+ end
125
+
126
+ # returns true if the archive is valid
127
+ def valid_archive?(stream)
128
+ begin
129
+ restore!(stream, :check => true)
130
+ rescue
131
+ return false
132
+ end
133
+ true
134
+ end
135
+
136
+ # generates an object from the archived hash
137
+ def from_archive(hash)
138
+ new.tap do |o|
139
+ o.id = hash.keys.first
140
+ o.attributes = hash.values.first[model_name.element]
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def archive_read_int(stream)
147
+ stream.read(4).unpack("N").first
148
+ end
149
+
150
+ def archive_write_int(stream, int)
151
+ stream.write([int].pack("N"))
152
+ end
153
+ end
154
+
155
+ # instance method to return archived hash
156
+ def as_archive
157
+ # force include_root_in_json
158
+ # allows archives to include multiple object types
159
+ if include_root_in_json
160
+ {id => as_json}
161
+ else
162
+ {id => {self.class.model_name.element => as_json}}
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'active_model_archive'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+ config.mock_with :rspec
12
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_model_archive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Grant Rodgers
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-03-09 00:00:00.000000000 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ requirement: &14070060 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *14070060
26
+ - !ruby/object:Gem::Dependency
27
+ name: yajl-ruby
28
+ requirement: &14069320 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *14069320
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &14068620 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: '2'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *14068620
48
+ description: Adds dump and restore functionality to activemodel classes
49
+ email:
50
+ - grantr@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - .rspec
57
+ - Gemfile
58
+ - Rakefile
59
+ - active_model_archive.gemspec
60
+ - lib/active_model_archive.rb
61
+ - lib/active_model_archive/version.rb
62
+ - spec/archive_spec.rb
63
+ - spec/spec_helper.rb
64
+ has_rdoc: true
65
+ homepage: ''
66
+ licenses: []
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project: active_model_archive
85
+ rubygems_version: 1.6.0
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Dump and restore activemodel objects
89
+ test_files:
90
+ - spec/archive_spec.rb
91
+ - spec/spec_helper.rb