swim 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,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
19
+ lib/.DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in swim.gemspec
4
+ gemspec
5
+
6
+ gem 'activesupport'
7
+ gem 'activerecord'
8
+ gem "sqlite3"
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Disruptive Ventures, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Swim
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'swim'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install swim
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
data/lib/swim.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Swim
2
+ require "swim/version"
3
+ require "swim/change"
4
+ require "swim/sync_tools"
5
+
6
+ def self.version_string
7
+ return "Swim version #{Swim::VERSION}"
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ class Swim::Change
2
+ attr_accessor :obj_class, :obj_id, :change_type, :key, :old_value, :new_value, :status, :errors
3
+
4
+ def initialize(args)
5
+ obj_class = args[:obj_class]
6
+ obj_id = args[:obj_id]
7
+ change_type = args[:change_type]
8
+ key = args[:key]
9
+ old_value = args[:old_value]
10
+ new_value = args[:new_value]
11
+ status = args[:status]
12
+ errors = args[:errors]
13
+ end
14
+
15
+ def new(args)
16
+ end
17
+
18
+ def item
19
+ obj_class.constantize.send(:find, obj_id)
20
+ end
21
+
22
+ def update(i)
23
+ i.update_attribute(key, new_value)
24
+ end
25
+
26
+ def delete(i)
27
+ i.destroy
28
+ end
29
+
30
+ def insert
31
+ i = obj_class.constantize.send(:new, new_value)
32
+ return i
33
+ end
34
+
35
+ def process
36
+ if change_type == :update
37
+ i = item
38
+ status = update(i) ? "succeeded" : "failed"
39
+ elsif change_type == :delete
40
+ i = item
41
+ status = delete(i) ? "succeeded" : "failed"
42
+ elsif change_type == :insert
43
+ i = insert
44
+ if insert.save
45
+ status = "succeeded"
46
+ else
47
+ errors = i.errors
48
+ status = "failed"
49
+ end
50
+ end
51
+ if status == "succeeded"
52
+ return true
53
+ else
54
+ errors = item.errors
55
+ return false
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,106 @@
1
+ require 'active_support/all'
2
+
3
+ class SyncSettingsMissing < StandardError
4
+ end
5
+
6
+ class SettingsFileMissing < StandardError
7
+ end
8
+
9
+
10
+ class SyncTools
11
+
12
+ def self.json_file(settings_filename)
13
+ file = File.open(settings_filename, "rb")
14
+ contents = file.read
15
+ ActiveSupport::JSON.decode(contents)
16
+ end
17
+
18
+ def self.compare_json_file(obj)
19
+ unless obj.methods.include?(:settings_file) || obj.methods.include?('settings_file')
20
+ raise SettingsFileMissing, "#{obj.class.to_s} must define a class method named settings_file."
21
+ end
22
+ json = json_file(obj.settings_file)
23
+
24
+ unless obj.class.methods.include?(:sync_settings) || obj.class.methods.include?("sync_settings")
25
+ raise SyncSettingsMissing, "#{obj.class.to_s} must define a class method named sync_settings that returns a hash."
26
+ end
27
+
28
+ if json[obj.class.to_s.underscore].class.to_s == "Hash"
29
+ return compare(obj, json[obj.class.to_s.underscore], obj.class.sync_settings)
30
+ else
31
+ return compare(obj, json, obj.class.sync_settings)
32
+ end
33
+ end
34
+
35
+ def self.import_json_file(obj, settings_filename)
36
+ changes = Swim::SyncTools.compare_json_file(self, settings_file)
37
+ completed = []
38
+ not_completed = []
39
+ changes.each do |ch|
40
+ oc = ch.process
41
+ if oc
42
+ completed << ch
43
+ else
44
+ not_completed << ch
45
+ end
46
+ end
47
+ return { :status => (not_completed.length > 0 ? "incomplete" : "complete"), :changed => completed, :not_completed => not_completed }
48
+ end
49
+
50
+ def self.compare(obj, hsh, settings)
51
+ @changes = []
52
+ if settings.present? && settings[:include]
53
+ settings[:include].keys.each do |sk|
54
+ compare_array(obj.send(sk), hsh[sk.to_s], settings[sk], sk)
55
+ end
56
+ elsif settings.present? && settings.keys
57
+ settings.keys.each do |sk|
58
+ compare_array(obj.send(sk), hsh[sk.to_s], settings[sk.to_sym], sk)
59
+ end
60
+ end
61
+
62
+ obj.attributes.each_pair do |key, value|
63
+ hsh_value = hsh[key]
64
+ if value.class == Time && hsh_value.class == String
65
+ hsh_value = Time.parse(hsh_value).utc.to_s
66
+ value = value.utc.to_s
67
+ elsif hsh_value.class == Time && value.class == String
68
+ hsh_value = hsh_value.utc.to_s
69
+ value = Time.parse(value).utc.to_s
70
+ elsif hsh_value.class == Time && value.class == Time
71
+ hsh_value = hsh_value.utc.to_s
72
+ value = value.utc.to_s
73
+ end
74
+ unless value == hsh_value
75
+ @changes << Swim::Change.new(:obj_class => obj.class.to_s, :obj_id => obj.id, :change_type => :update, :key => key, :old_value => value, :new_value => hsh_value)
76
+ else
77
+ # p "#{obj.class.to_s} #{key} #{value} == #{hsh_value}"
78
+ end
79
+ end
80
+ return @changes
81
+ end
82
+
83
+ def self.compare_array(arr, hsh, settings, obj_classname)
84
+ if arr.length == 0 && hsh.nil?
85
+ return
86
+ elsif hsh.nil?
87
+ throw "Array (#{arr.collect{|ar| ar.class.to_s }}) has no hash for comparison"
88
+ end
89
+ hsh_members = hsh.collect{ |h| h["id"] }
90
+ arr.each do |a|
91
+ x = hsh.select{ |h| h if h["id"] == a.id }[0]
92
+ hsh_members.delete(x["id"])
93
+ if x.nil? || x.empty?
94
+ @changes << Change.new(:obj_class => a.class.to_s.singularize.capitalize, :obj_id => a.id, :change_type => :delete)
95
+ else
96
+ compare(a, x, settings)
97
+ end
98
+ end
99
+ if hsh_members.length > 0
100
+ hsh_members.each do |hm|
101
+ @changes << Change.new(:obj_class => obj_classname.to_s.singularize.capitalize, :obj_id => hm, :change_type => :insert, :new_value => hsh.select{ |h| h if h["id"] == hm }[0].to_json)
102
+ end
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,3 @@
1
+ module Swim
2
+ VERSION = "0.0.1"
3
+ end
Binary file
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'swim'
3
+
4
+ describe Swim do
5
+ it 'should return correct version string' do
6
+ Swim.version_string.should == "Swim version #{Swim::VERSION}"
7
+ end
8
+ end
@@ -0,0 +1,49 @@
1
+ # sync_tools_spec.rb
2
+ require 'spec_helper'
3
+ require 'swim/change'
4
+ require 'swim/sync_tools'
5
+
6
+ class AlmostValidClass
7
+ def settings_file
8
+ File.expand_path("#{__FILE__}/../../sample_files/almost_valid_class_settings.json")
9
+ end
10
+
11
+ # def self.sync_settings
12
+ # return Hash.new
13
+ # end
14
+ end
15
+
16
+ class InvalidClass
17
+ # def self.settings_file
18
+ # return Hash.new
19
+ # end
20
+ # def self.sync_settings
21
+ # return Hash.new
22
+ # end
23
+ end
24
+
25
+ describe SyncTools do
26
+
27
+ it "should require the object to define settings_file" do
28
+ i_class = InvalidClass.new
29
+ expect { SyncTools::compare_json_file(i_class) }.to raise_error(
30
+ SettingsFileMissing
31
+ )
32
+ end
33
+
34
+ it "should require the object to define sync_settings" do
35
+ i2_class = AlmostValidClass.new
36
+ expect { SyncTools::compare_json_file(i2_class) }.to raise_error(
37
+ SyncSettingsMissing
38
+ )
39
+ end
40
+
41
+ it "should return a change when one exists" do
42
+ artist = Artist.find(1)
43
+ changes = SyncTools::compare_json_file(artist)
44
+ changes.should be_an_instance_of(Array)
45
+ changes.length.should == 1
46
+ changes.first.should be_an_instance_of(Swim::Change)
47
+ end
48
+
49
+ end
@@ -0,0 +1 @@
1
+ {"almost_valid_class":[{ "id": "1", "value": "File One" }, { "id": "2", "value": "File Two" }]}
@@ -0,0 +1 @@
1
+ {"artist":{"created_at":"2012-07-03T14:02:14-04:00","id":1,"name":"Billy Jail","updated_at":"2012-07-03T14:02:14-04:00","albums":[{"artist_id":1,"created_at":"2012-07-03T14:02:13-04:00","id":1,"title":"The Stranger","updated_at":"2012-07-03T14:02:14-04:00"}]}}
@@ -0,0 +1,4 @@
1
+ class Album < ActiveRecord::Base
2
+ belongs_to :artist
3
+ has_many :tracks
4
+ end
@@ -0,0 +1,11 @@
1
+ class Artist < ActiveRecord::Base
2
+ has_many :albums
3
+
4
+ def settings_file
5
+ File.expand_path("#{__FILE__}/../../sample_files/artist_settings.json")
6
+ end
7
+
8
+ def self.sync_settings
9
+ { :albums => {} }
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ # config.filter_run :focus
11
+ end
12
+
13
+
14
+ require 'active_record'
15
+
16
+ ActiveRecord::Base.establish_connection(
17
+ { :adapter => 'sqlite3', :database => File.dirname(__FILE__) + "/db/development.sqlite3" }
18
+ )
19
+
20
+ Dir[File.dirname(__FILE__) + '/sample_models/*.rb'].each do |file|
21
+ require file
22
+ end
data/swim.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/swim/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jerry Richardson"]
6
+ gem.email = ["jerry@disruptiveventures.com"]
7
+ gem.description = %q{Sync or Swim}
8
+ gem.summary = %q{Synchronize ActiveRecord Objects Across Environments with JSON and git}
9
+ gem.homepage = ""
10
+
11
+ gem.add_development_dependency 'rspec'
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "swim"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = Swim::VERSION
18
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swim
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jerry Richardson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Sync or Swim
31
+ email:
32
+ - jerry@disruptiveventures.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rspec
39
+ - Gemfile
40
+ - LICENSE
41
+ - README.md
42
+ - Rakefile
43
+ - lib/swim.rb
44
+ - lib/swim/change.rb
45
+ - lib/swim/sync_tools.rb
46
+ - lib/swim/version.rb
47
+ - spec/db/development.sqlite3
48
+ - spec/lib/swim_spec.rb
49
+ - spec/lib/sync_tools_spec.rb
50
+ - spec/sample_files/almost_valid_class_settings.json
51
+ - spec/sample_files/artist_settings.json
52
+ - spec/sample_models/album.rb
53
+ - spec/sample_models/artist.rb
54
+ - spec/spec_helper.rb
55
+ - swim.gemspec
56
+ homepage: ''
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.23
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Synchronize ActiveRecord Objects Across Environments with JSON and git
80
+ test_files:
81
+ - spec/db/development.sqlite3
82
+ - spec/lib/swim_spec.rb
83
+ - spec/lib/sync_tools_spec.rb
84
+ - spec/sample_files/almost_valid_class_settings.json
85
+ - spec/sample_files/artist_settings.json
86
+ - spec/sample_models/album.rb
87
+ - spec/sample_models/artist.rb
88
+ - spec/spec_helper.rb