active_model_archive 0.0.1

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,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