nsync 0.0.2
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/LICENSE +21 -0
- data/README.md +204 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/jeweler_monkey_patch.rb +59 -0
- data/lib/nsync/active_record/consumer/methods.rb +47 -0
- data/lib/nsync/active_record/methods.rb +23 -0
- data/lib/nsync/active_record/producer/methods.rb +21 -0
- data/lib/nsync/class_methods.rb +17 -0
- data/lib/nsync/config.rb +96 -0
- data/lib/nsync/consumer.rb +293 -0
- data/lib/nsync/git_version_manager.rb +29 -0
- data/lib/nsync/producer/methods.rb +23 -0
- data/lib/nsync/producer.rb +129 -0
- data/lib/nsync.rb +30 -0
- data/nsync.gemspec +80 -0
- data/test/active_record_test.rb +153 -0
- data/test/classes.rb +22 -0
- data/test/helper.rb +12 -0
- data/test/nsync_config_test.rb +142 -0
- data/test/nsync_consumer_test.rb +260 -0
- data/test/nsync_producer_test.rb +261 -0
- data/test/repo.rb +64 -0
- metadata +165 -0
@@ -0,0 +1,293 @@
|
|
1
|
+
module Nsync
|
2
|
+
# The Nsync::Consumer is used to handle the consumption of data from an Nsync
|
3
|
+
# repo for the entire app. It reads in the differences between the current
|
4
|
+
# version of data in the database and the new data from the producer, finding
|
5
|
+
# and notifying all affected classes and objects.
|
6
|
+
#
|
7
|
+
# Basic Usage:
|
8
|
+
#
|
9
|
+
# Nsync::Config.run do |c|
|
10
|
+
# # The consumer uses a read-only, bare repository (one ending in .git)
|
11
|
+
# # This will automatically be created if it does not exist
|
12
|
+
# c.repo_path = "/local/path/to/hold/data.git"
|
13
|
+
# # The remote repository url from which to pull data
|
14
|
+
# c.repo_url = "git@examplegithost:username/data.git"
|
15
|
+
#
|
16
|
+
# # An object that implements the VersionManager interface
|
17
|
+
# # (see Nsync::GitVersionManager) for an example
|
18
|
+
# c.version_manager = MyCustomVersionManager.new
|
19
|
+
#
|
20
|
+
# # A lock file path to use for this app
|
21
|
+
# c.lock_file = "/tmp/app_name_nsync.lock"
|
22
|
+
#
|
23
|
+
# # The class mapping maps from the class names of the producer classes to
|
24
|
+
# # the class names of their associated consuming classes. A producer can
|
25
|
+
# # map to one or many consumers, and a consumer can be mapped to one or many
|
26
|
+
# # producers. Consumer classes should implement the Consumer interface.
|
27
|
+
# c.map_class "RawDataPostClass", "Post"
|
28
|
+
# c.map_class "RawDataInfo", "Info"
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Create a new consumer object, this will clone the repo if needed
|
32
|
+
# consumer = Nsync::Consumer.new
|
33
|
+
#
|
34
|
+
# # update this app to the latest data, pulling if necessary
|
35
|
+
# consumer.update
|
36
|
+
#
|
37
|
+
# # rollback the last change
|
38
|
+
# consumer.rollback
|
39
|
+
class Consumer
|
40
|
+
attr_accessor :repo
|
41
|
+
|
42
|
+
# There was an issue creating or accessing the repository
|
43
|
+
class CouldNotInitializeRepoError < RuntimeError; end
|
44
|
+
|
45
|
+
# Sets the repository to the repo at config.repo_path
|
46
|
+
#
|
47
|
+
# If config.repo_url is set and the directory at config.repo_path does not
|
48
|
+
# exist yet, a new bare repository will be cloned from config.repo_url
|
49
|
+
def initialize
|
50
|
+
unless get_or_create_repo
|
51
|
+
raise CouldNotInitializeRepoError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Updates the data to the latest version
|
56
|
+
#
|
57
|
+
# If the repo has a remote origin, the latest changes will be fetched.
|
58
|
+
#
|
59
|
+
# NOTE: It is critical that the version_manager returns correct results
|
60
|
+
# as this method goes from what it says is the latest commit that was loaded in
|
61
|
+
# to HEAD.
|
62
|
+
def update
|
63
|
+
update_repo &&
|
64
|
+
apply_changes(config.version_manager.version,
|
65
|
+
repo.head.commit.id)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Rolls back data to the previous loaded version
|
69
|
+
#
|
70
|
+
# NOTE: If you rollback and then update, the 'bad' commit will then be reloaded.
|
71
|
+
# This is primarily meant as a way to get back to a known good state quickly, while
|
72
|
+
# the issues are fixed in the producer.
|
73
|
+
def rollback
|
74
|
+
apply_changes(config.version_manager.version,
|
75
|
+
config.version_manager.previous_version)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Nsync::Config]
|
79
|
+
def config
|
80
|
+
Nsync.config
|
81
|
+
end
|
82
|
+
|
83
|
+
# Translates and applies the changes between commit id 'a' and commit id 'b' to
|
84
|
+
# the datastore. This is used internally by rollback and update. Don't use this
|
85
|
+
# unless you absolutely know what you are doing.
|
86
|
+
#
|
87
|
+
# If you must call this directly, understand that 'a' should almost always be the
|
88
|
+
# commit id of the current data that is loaded into the database. 'b' can be any
|
89
|
+
# commit in the graph, forward or backwards.
|
90
|
+
#
|
91
|
+
# @param [String] a current data version commit id
|
92
|
+
# @param [String] b new data version commit id
|
93
|
+
def apply_changes(a, b)
|
94
|
+
config.lock do
|
95
|
+
config.log.info("[NSYNC] Moving Nsync::Consumer from '#{a}' to '#{b}'")
|
96
|
+
clear_queues
|
97
|
+
diffs = nil
|
98
|
+
diffs = repo.diff(a, b)
|
99
|
+
|
100
|
+
changeset = changeset_from_diffs(diffs)
|
101
|
+
|
102
|
+
if config.ordering
|
103
|
+
config.ordering.each do |klass|
|
104
|
+
begin
|
105
|
+
klass = klass.constantize
|
106
|
+
changes = changeset[klass]
|
107
|
+
if changes
|
108
|
+
apply_changes_for_class(klass, changes)
|
109
|
+
end
|
110
|
+
rescue NameError
|
111
|
+
config.log.warn("[NSYNC] Could not find class '#{klass}' from ordering; skipping")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
changeset.each do |klass, changes|
|
116
|
+
apply_changes_for_class(klass, changes)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
run_after_finished
|
120
|
+
clear_queues
|
121
|
+
config.version_manager.version = b
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# @private
|
127
|
+
class Change < Struct.new(:id, :diff)
|
128
|
+
def type
|
129
|
+
if diff.deleted_file
|
130
|
+
:deleted
|
131
|
+
elsif diff.new_file
|
132
|
+
:added
|
133
|
+
else
|
134
|
+
:modified
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def data
|
139
|
+
@data ||= JSON.load(diff.b_blob.data)
|
140
|
+
rescue
|
141
|
+
{}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Adds a callback to the list of callbacks to occur after main processing
|
146
|
+
# of the class specified by 'klass'. Can be used to handle data relations
|
147
|
+
# between objects of the same class.
|
148
|
+
#
|
149
|
+
# Example:
|
150
|
+
#
|
151
|
+
# class Post
|
152
|
+
# def nsync_update(consumer, event_type, filename, data)
|
153
|
+
# #... normal data update stuff ...
|
154
|
+
# post = self
|
155
|
+
# related_post_source_ids = data['related_post_ids']
|
156
|
+
# consumer.after_class_finished(Post, lambda {
|
157
|
+
# posts = Post.all(:conditions =>
|
158
|
+
# {:source_id => related_post_source_ids })
|
159
|
+
# post.related_posts = posts
|
160
|
+
# })
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# @param [Class] klass
|
165
|
+
# @param [Proc] l
|
166
|
+
def after_class_finished(klass, l)
|
167
|
+
@after_class_finished_queues[klass] ||= []
|
168
|
+
@after_class_finished_queues[klass] << l
|
169
|
+
end
|
170
|
+
|
171
|
+
# Adds a callback to the list of callbacks to occur after main processing
|
172
|
+
# of the class that is currently being processed. This is essentially an
|
173
|
+
# alias for after_class_finished for the current class
|
174
|
+
#
|
175
|
+
# @param [Proc] l
|
176
|
+
def after_current_class_finished(l)
|
177
|
+
after_class_finished(@current_class_for_queue, l)
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# Adds a callback to the list of callbacks to occur after all changes have
|
182
|
+
# been applied. This queue executes immediately prior to the current
|
183
|
+
# version being updated
|
184
|
+
#
|
185
|
+
# @param [Proc] l
|
186
|
+
def after_finished(l)
|
187
|
+
@after_finished_queue ||= []
|
188
|
+
@after_finished_queue << l
|
189
|
+
end
|
190
|
+
|
191
|
+
protected
|
192
|
+
def get_or_create_repo
|
193
|
+
if config.local? || File.exists?(config.repo_path)
|
194
|
+
return self.repo = Grit::Repo.new(config.repo_path)
|
195
|
+
end
|
196
|
+
|
197
|
+
config.lock do
|
198
|
+
git = Grit::Git.new(config.repo_path)
|
199
|
+
git.clone({:bare => true}, config.repo_url, config.repo_path)
|
200
|
+
self.repo = Grit::Repo.new(config.repo_path)
|
201
|
+
config.version_manager.version = git.rev_list({:reverse => true}, "master").split("\n").first
|
202
|
+
return self.repo
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def update_repo
|
207
|
+
return true if config.local?
|
208
|
+
config.lock do
|
209
|
+
repo.remote_fetch('origin')
|
210
|
+
# from http://www.pragmatic-source.com/en/opensource/tips/automatic-synchronization-2-git-repositories
|
211
|
+
repo.git.reset({:soft => true}, 'FETCH_HEAD')
|
212
|
+
true
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def apply_changes_for_class(klass, changes)
|
217
|
+
@current_class_for_queue = klass
|
218
|
+
if klass.respond_to?(:nsync_find)
|
219
|
+
changes.each do |change|
|
220
|
+
objects = klass.nsync_find(change.id)
|
221
|
+
if objects.empty? && change.type != :deleted
|
222
|
+
if klass.respond_to?(:nsync_add_data)
|
223
|
+
config.log.info("[NSYNC] Adding data #{diff_path(change.diff)} to #{klass}")
|
224
|
+
klass.nsync_add_data(self, change.type, diff_path(change.diff), change.data)
|
225
|
+
else
|
226
|
+
config.log.warn("[NSYNC] Class '#{klass}' has no method nsync_add_data; skipping")
|
227
|
+
end
|
228
|
+
else
|
229
|
+
objects.each do |obj|
|
230
|
+
if obj.respond_to?(:nsync_update)
|
231
|
+
obj.nsync_update(self, change.type, diff_path(change.diff),
|
232
|
+
change.data)
|
233
|
+
config.log.info("[NSYNC] Updating from #{diff_path(change.diff)} to #{obj.inspect}")
|
234
|
+
else
|
235
|
+
config.log.info("[NSYNC] Object #{obj.inspect} has no method nsync_update; skipping")
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
else
|
241
|
+
config.log.warn("[NSYNC] Consumer class '#{klass}' has no method nsync_find; skipping")
|
242
|
+
end
|
243
|
+
@current_class_for_queue = nil
|
244
|
+
run_after_class_finished(klass)
|
245
|
+
end
|
246
|
+
|
247
|
+
def clear_queues
|
248
|
+
@after_class_finished_queues = {}
|
249
|
+
@after_finished_queue = []
|
250
|
+
end
|
251
|
+
|
252
|
+
def run_after_class_finished(klass)
|
253
|
+
queue = @after_class_finished_queues[klass]
|
254
|
+
if queue
|
255
|
+
queue.each do |l|
|
256
|
+
l.call
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def run_after_finished
|
262
|
+
if @after_finished_queue
|
263
|
+
@after_finished_queue.each do |l|
|
264
|
+
l.call
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def changeset_from_diffs(diffs)
|
270
|
+
diffs.inject({}) do |h, diff|
|
271
|
+
next h if diff_path(diff) =~ /\.gitignore$/
|
272
|
+
|
273
|
+
classes, id = consumer_classes_and_id_from_path(diff_path(diff))
|
274
|
+
classes.each do |klass|
|
275
|
+
h[klass] ||= []
|
276
|
+
h[klass] << Change.new(id, diff)
|
277
|
+
end
|
278
|
+
h
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def consumer_classes_and_id_from_path(path)
|
283
|
+
producer_class_name = File.dirname(path).camelize
|
284
|
+
id = File.basename(path, ".json")
|
285
|
+
[config.consumer_classes_for(producer_class_name), id]
|
286
|
+
end
|
287
|
+
|
288
|
+
def diff_path(diff)
|
289
|
+
diff.b_path || diff.a_path
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Nsync
|
2
|
+
class GitVersionManager
|
3
|
+
def initialize(repo_path = nil)
|
4
|
+
@repo_path = repo_path
|
5
|
+
end
|
6
|
+
|
7
|
+
def repo
|
8
|
+
# if the repo is not ready, lets just hope it works
|
9
|
+
@repo ||= Grit::Repo.new(@repo_path || Nsync.config.repo_path) rescue nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def version
|
13
|
+
repo.head.commit.id if repo
|
14
|
+
end
|
15
|
+
|
16
|
+
def version=(val)
|
17
|
+
val
|
18
|
+
end
|
19
|
+
|
20
|
+
def previous_version
|
21
|
+
previous_versions[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous_versions
|
25
|
+
repo.commits("master", 10, 1).map(&:id) if repo
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Nsync
|
2
|
+
class Producer
|
3
|
+
module InstanceMethods
|
4
|
+
def nsync_write
|
5
|
+
nsync_opts = self.class.read_inheritable_attribute(:nsync_opts)
|
6
|
+
if !nsync_opts[:if] || nsync_opts[:if].call(self)
|
7
|
+
Nsync.config.producer_instance.write_file(nsync_filename, to_nsync_hash)
|
8
|
+
elsif Nsync.config.producer_instance.file_exists?(nsync_filename)
|
9
|
+
nsync_destroy
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def nsync_destroy
|
14
|
+
Nsync.config.producer_instance.remove_file(nsync_filename)
|
15
|
+
end
|
16
|
+
|
17
|
+
def nsync_filename
|
18
|
+
nsync_opts = self.class.read_inheritable_attribute(:nsync_opts)
|
19
|
+
File.join(self.class.to_s.underscore, "#{send(nsync_opts[:id_key])}.json")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Nsync
|
2
|
+
# The Nsync::Producer is used to tend to the repo and allow files to be
|
3
|
+
# written out and changesets to be commited. It is a subclass of the
|
4
|
+
# Nsync::Consumer, which allows a Producer to consume itself. This gives it
|
5
|
+
# the ability to perform rollbacks and undo misstakes in that way
|
6
|
+
#
|
7
|
+
# Basic Usage:
|
8
|
+
# Nsync::Config.run do |c|
|
9
|
+
# # The producer uses a standard repository
|
10
|
+
# # This will automatically be created if it does not exist
|
11
|
+
# c.repo_path = "/local/path/to/hold/data"
|
12
|
+
# # The remote repository url will get data pushed to it
|
13
|
+
# c.repo_push_url = "git@examplegithost:username/data.git"
|
14
|
+
#
|
15
|
+
# # This must be Nsync::GitVersionManager if you want things like
|
16
|
+
# # rollback to work.
|
17
|
+
# c.version_manager = Nsync::GitVersionManager.new
|
18
|
+
#
|
19
|
+
# # A lock file path to use for this app
|
20
|
+
# c.lock_file = "/tmp/app_name_nsync.lock"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # make some changes that get written out to the repo
|
24
|
+
#
|
25
|
+
# @producer = Nsync::Producer.new
|
26
|
+
#
|
27
|
+
# @producer.commit("Some nice changes for you")
|
28
|
+
class Producer < Consumer
|
29
|
+
# Determines whether a file at 'filename' exists in the working tree
|
30
|
+
def file_exists?(filename)
|
31
|
+
File.exists?(File.join(config.repo_path, filename))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Writes a file to the repo at 'filename' with content in json from the
|
35
|
+
# hash
|
36
|
+
#
|
37
|
+
# @param [String] filename path in the working tree to write to
|
38
|
+
# @param [Hash] hash a hash that can be converted to json to be written
|
39
|
+
def write_file(filename, hash)
|
40
|
+
config.cd do
|
41
|
+
dir = File.dirname(filename)
|
42
|
+
unless [".", "/"].include?(dir) || File.directory?(dir)
|
43
|
+
FileUtils.mkdir_p(File.join(config.repo_path, dir))
|
44
|
+
end
|
45
|
+
|
46
|
+
File.open(File.join(config.repo_path, filename), "w") do |f|
|
47
|
+
f.write( (hash.is_a?(Hash))? hash.to_json : hash )
|
48
|
+
end
|
49
|
+
repo.add(File.join(config.repo_path, filename))
|
50
|
+
config.log.info("[NSYNC] Updated file '#{filename}'")
|
51
|
+
end
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes a file from the repo at 'filename'
|
56
|
+
def remove_file(filename)
|
57
|
+
FileUtils.rm File.join(config.repo_path, filename)
|
58
|
+
config.log.info("[NSYNC] Removed file '#{filename}'")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Commits and pushes the current changeset
|
62
|
+
def commit(message="Friendly data update")
|
63
|
+
config.lock do
|
64
|
+
config.cd do
|
65
|
+
repo.commit_all(message)
|
66
|
+
config.log.info("[NSYNC] Committed '#{message}' to repo")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
push
|
70
|
+
end
|
71
|
+
|
72
|
+
# Pushes all changes to the repo_push_url
|
73
|
+
def push
|
74
|
+
if config.remote_push?
|
75
|
+
config.cd do
|
76
|
+
repo.git.push({}, config.repo_push_url, "+master")
|
77
|
+
config.log.info("[NSYNC] Pushed changes")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns data to its state at HEAD~1 and sets HEAD to that
|
84
|
+
# This new HEAD state is pushed to the repo_push_url. Hooray git.
|
85
|
+
def rollback
|
86
|
+
commit_to_rollback = config.version_manager.version
|
87
|
+
commit_to_rollback_to = config.version_manager.previous_version
|
88
|
+
config.cd do
|
89
|
+
repo.git.reset({:hard => true}, commit_to_rollback_to)
|
90
|
+
apply_changes(commit_to_rollback, commit_to_rollback_to)
|
91
|
+
end
|
92
|
+
push
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def update(*args)
|
98
|
+
super
|
99
|
+
end
|
100
|
+
|
101
|
+
def apply_changes(*args)
|
102
|
+
super
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_or_create_repo
|
106
|
+
if File.exists?(config.repo_path)
|
107
|
+
return self.repo = Grit::Repo.new(config.repo_path)
|
108
|
+
end
|
109
|
+
|
110
|
+
config.lock do
|
111
|
+
self.repo = Grit::Repo.init(config.repo_path)
|
112
|
+
write_file(".gitignore", "")
|
113
|
+
end
|
114
|
+
commit("Initial Commit")
|
115
|
+
end
|
116
|
+
|
117
|
+
def consumer_classes_and_id_from_path(path)
|
118
|
+
producer_class_name = File.dirname(path).camelize
|
119
|
+
id = File.basename(path, ".json")
|
120
|
+
classes = config.consumer_classes_for(producer_class_name)
|
121
|
+
|
122
|
+
# refinement to allow the producer to consume itself
|
123
|
+
if classes.empty?
|
124
|
+
classes = [producer_class_name.constantize].compact
|
125
|
+
end
|
126
|
+
[classes, id]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/nsync.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# just for now
|
5
|
+
gem 'activesupport', "~> 2.3.5"
|
6
|
+
require 'active_support'
|
7
|
+
|
8
|
+
gem "schleyfox-grit", ">= 2.3.0.1"
|
9
|
+
require 'grit'
|
10
|
+
|
11
|
+
#up the timeout, as these repos can get quite large
|
12
|
+
Grit::Git.git_timeout = 60 # 1 minute should do
|
13
|
+
Grit::Git.git_max_size = 100.megabytes # tweak this up for very large changesets
|
14
|
+
|
15
|
+
gem "schleyfox-lockfile", ">= 1.0.0"
|
16
|
+
require 'lockfile'
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'yajl/json_gem'
|
20
|
+
rescue LoadError
|
21
|
+
puts "Yajl not installed; falling back to json"
|
22
|
+
require 'json'
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'nsync/config'
|
26
|
+
require 'nsync/consumer'
|
27
|
+
require 'nsync/git_version_manager'
|
28
|
+
require 'nsync/producer'
|
29
|
+
require 'nsync/class_methods'
|
30
|
+
require 'nsync/active_record/methods.rb'
|
data/nsync.gemspec
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{nsync}
|
8
|
+
s.version = "0.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ben Hughes"]
|
12
|
+
s.date = %q{2010-12-06}
|
13
|
+
s.description = %q{Nsync is designed to allow you to have a separate data
|
14
|
+
processing app with its own data processing optimized database and a consumer
|
15
|
+
app with its own database, while keeping the data as in sync as you want it.}
|
16
|
+
s.email = %q{ben@pixelmachine.org}
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"LICENSE",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"jeweler_monkey_patch.rb",
|
27
|
+
"lib/nsync.rb",
|
28
|
+
"lib/nsync/active_record/consumer/methods.rb",
|
29
|
+
"lib/nsync/active_record/methods.rb",
|
30
|
+
"lib/nsync/active_record/producer/methods.rb",
|
31
|
+
"lib/nsync/class_methods.rb",
|
32
|
+
"lib/nsync/config.rb",
|
33
|
+
"lib/nsync/consumer.rb",
|
34
|
+
"lib/nsync/git_version_manager.rb",
|
35
|
+
"lib/nsync/producer.rb",
|
36
|
+
"lib/nsync/producer/methods.rb",
|
37
|
+
"nsync.gemspec"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/schleyfox/nsync}
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubygems_version = %q{1.3.6}
|
42
|
+
s.summary = %q{Keep your data processors and apps in sync}
|
43
|
+
s.test_files = [
|
44
|
+
"test/active_record_test.rb",
|
45
|
+
"test/classes.rb",
|
46
|
+
"test/helper.rb",
|
47
|
+
"test/nsync_config_test.rb",
|
48
|
+
"test/nsync_consumer_test.rb",
|
49
|
+
"test/nsync_producer_test.rb",
|
50
|
+
"test/repo.rb"
|
51
|
+
]
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
58
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
59
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 2.3.5"])
|
60
|
+
s.add_runtime_dependency(%q<schleyfox-grit>, [">= 2.3.0.1"])
|
61
|
+
s.add_runtime_dependency(%q<schleyfox-lockfile>, [">= 1.0.0"])
|
62
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<json>, [">= 0"])
|
66
|
+
s.add_dependency(%q<activesupport>, ["~> 2.3.5"])
|
67
|
+
s.add_dependency(%q<schleyfox-grit>, [">= 2.3.0.1"])
|
68
|
+
s.add_dependency(%q<schleyfox-lockfile>, [">= 1.0.0"])
|
69
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
70
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<json>, [">= 0"])
|
74
|
+
s.add_dependency(%q<activesupport>, ["~> 2.3.5"])
|
75
|
+
s.add_dependency(%q<schleyfox-grit>, [">= 2.3.0.1"])
|
76
|
+
s.add_dependency(%q<schleyfox-lockfile>, [">= 1.0.0"])
|
77
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
78
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
79
|
+
end
|
80
|
+
end
|