dandelion 0.3.15 → 0.4.0.beta2
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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +11 -0
- data/LICENSE +1 -1
- data/README.md +15 -26
- data/Rakefile +4 -8
- data/bin/dandelion +4 -3
- data/dandelion.gemspec +23 -20
- data/lib/dandelion.rb +26 -10
- data/lib/dandelion/adapter.rb +45 -0
- data/lib/dandelion/{backend → adapter}/ftp.rb +22 -17
- data/lib/dandelion/adapter/noop.rb +14 -0
- data/lib/dandelion/{backend → adapter}/s3.rb +14 -15
- data/lib/dandelion/{backend → adapter}/sftp.rb +29 -16
- data/lib/dandelion/change.rb +11 -0
- data/lib/dandelion/changeset.rb +49 -0
- data/lib/dandelion/cli.rb +127 -0
- data/lib/dandelion/command.rb +26 -92
- data/lib/dandelion/command/deploy.rb +54 -41
- data/lib/dandelion/command/status.rb +14 -13
- data/lib/dandelion/config.rb +36 -0
- data/lib/dandelion/deployer.rb +53 -0
- data/lib/dandelion/diff.rb +62 -0
- data/lib/dandelion/tree.rb +22 -0
- data/lib/dandelion/utils.rb +13 -0
- data/lib/dandelion/version.rb +2 -2
- data/lib/dandelion/workspace.rb +71 -0
- data/spec/dandelion/adapter_spec.rb +42 -0
- data/spec/dandelion/change_spec.rb +17 -0
- data/spec/dandelion/changeset_spec.rb +73 -0
- data/spec/dandelion/command/deploy_spec.rb +111 -0
- data/spec/dandelion/command/status_spec.rb +4 -0
- data/spec/dandelion/command_spec.rb +70 -0
- data/spec/dandelion/config_spec.rb +15 -0
- data/spec/dandelion/deployer_spec.rb +65 -0
- data/spec/dandelion/diff_spec.rb +54 -0
- data/spec/dandelion/tree_spec.rb +15 -0
- data/spec/dandelion/workspace_spec.rb +77 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/HEAD +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/config +1 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/description +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/applypatch-msg.sample +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/commit-msg.sample +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/post-update.sample +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/pre-applypatch.sample +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/pre-commit.sample +6 -2
- data/spec/fixtures/repo.git/hooks/pre-push.sample +53 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/pre-rebase.sample +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/prepare-commit-msg.sample +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/hooks/update.sample +1 -1
- data/{test/test_git.git → spec/fixtures/repo.git}/info/exclude +0 -0
- data/spec/fixtures/repo.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 +0 -0
- data/spec/fixtures/repo.git/objects/34/e64fe350f3ea80c989cdac7a99c2adb8574fca +0 -0
- data/spec/fixtures/repo.git/objects/3d/9b743acb4a84dd99002d2c6f3fcf1a47e9f06b +0 -0
- data/spec/fixtures/repo.git/objects/4e/44973d41d33bf5342037f56497efe0a9604d25 +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 +0 -0
- data/spec/fixtures/repo.git/objects/6c/a0f54491390579ce9438ec89c64c6b3499683a +0 -0
- data/spec/fixtures/repo.git/objects/8a/e33865a630c5d141c8a498f0c0166ff240b433 +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/objects/90/2dce0535b19f0c15ac8407fc4468256ad672d7 +0 -0
- data/spec/fixtures/repo.git/objects/c3/9af82404cc4267b1ba5f4b4437a511e0776abb +0 -0
- data/spec/fixtures/repo.git/objects/c8/85b3f693ed6e2926971ef75680f41b318072ae +0 -0
- data/spec/fixtures/repo.git/objects/d8/7cbcba0e2ede0752bdafc5938da35546803ba5 +0 -0
- data/spec/fixtures/repo.git/objects/e2/89ff1e2729839759dbd6fe99b6e35880910c7c +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
- data/{test/test_git.git → spec/fixtures/repo.git}/objects/ea/41dba10b54a794284e0be009a11f0ff3716a28 +0 -0
- data/spec/fixtures/repo.git/objects/ee/314a31b622b027c10981acaed7903a3607dbd4 +0 -0
- data/spec/fixtures/repo.git/objects/f6/66b137794d56880bab05e8fd256713a8fccf92 +0 -0
- data/spec/fixtures/repo.git/refs/heads/master +1 -0
- data/spec/spec_helper.rb +28 -0
- metadata +119 -127
- data/lib/dandelion/application.rb +0 -73
- data/lib/dandelion/backend.rb +0 -54
- data/lib/dandelion/deployment.rb +0 -173
- data/lib/dandelion/git.rb +0 -123
- data/test/fixtures/diff +0 -3
- data/test/fixtures/ls_tree +0 -5
- data/test/test_diff_deployment.rb +0 -122
- data/test/test_ftp.rb +0 -49
- data/test/test_git.git/hooks/post-commit.sample +0 -8
- data/test/test_git.git/hooks/post-receive.sample +0 -15
- data/test/test_git.git/objects/0c/a605e9f0f1d42ce8193ac36db11ec3cc9efc08 +0 -0
- data/test/test_git.git/objects/11/bada4e36fd065c8d1d3ca97b8dffa496c8e021 +0 -0
- data/test/test_git.git/objects/88/d4480861346093048e08ce8dcc577d8aa69379 +0 -1
- data/test/test_git.git/objects/a6/394b3e8a82b76b0dd5b6b317f489dfe22426a6 +0 -0
- data/test/test_git.git/objects/a6/5140d5ec9f47064f614ecf8e43776baa5c0c11 +0 -0
- data/test/test_git.git/objects/f5/5f3c44c89e5d215fbaaef9d33563117fe0b61b +0 -1
- data/test/test_git.git/objects/ff/1f1d4bd0c99e1c9cca047c46b2194accf89504 +0 -4
- data/test/test_git.git/refs/heads/master +0 -1
- data/test/test_git.rb +0 -50
- data/test/test_sftp.rb +0 -54
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
module Dandelion
|
|
2
2
|
module Command
|
|
3
3
|
class Status < Command::Base
|
|
4
|
-
command
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
opts.banner = 'Usage: dandelion status'
|
|
10
|
-
end
|
|
4
|
+
command :status
|
|
5
|
+
|
|
6
|
+
def self.parser(options)
|
|
7
|
+
OptionParser.new do |opts|
|
|
8
|
+
opts.banner = 'Usage: dandelion status'
|
|
11
9
|
end
|
|
12
10
|
end
|
|
13
|
-
|
|
14
|
-
def execute
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
|
|
12
|
+
def execute!
|
|
13
|
+
log.info("Connecting to #{adapter.to_s}")
|
|
14
|
+
|
|
15
|
+
local_commit = workspace.local_commit
|
|
16
|
+
remote_commit = workspace.remote_commit
|
|
17
|
+
|
|
18
|
+
log.info("Remote revision: #{remote_commit ? remote_commit.oid : '---'}")
|
|
19
|
+
log.info("Local HEAD revision: #{workspace.local_commit.oid}")
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Dandelion
|
|
4
|
+
class Config
|
|
5
|
+
attr_reader :path, :data
|
|
6
|
+
|
|
7
|
+
def initialize(options = {})
|
|
8
|
+
@path = options[:path]
|
|
9
|
+
@data = @path ? load : (options[:data] || {})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def [](key)
|
|
13
|
+
@data[key] || @data[key.to_s]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def []=(key, value)
|
|
17
|
+
@data[key.to_s] = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def defaults(values)
|
|
21
|
+
values.each do |k, v|
|
|
22
|
+
if self[k].nil?
|
|
23
|
+
self[k] = v
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def load
|
|
33
|
+
YAML.load_file(path) || {}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Dandelion
|
|
2
|
+
class Deployer
|
|
3
|
+
def initialize(adapter, options = {})
|
|
4
|
+
@adapter = adapter
|
|
5
|
+
@options = options
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def deploy_changeset!(changeset)
|
|
9
|
+
changeset.each do |change|
|
|
10
|
+
if exclude?(change.path)
|
|
11
|
+
log.debug("Skipping file: #{change.path}")
|
|
12
|
+
else
|
|
13
|
+
deploy_change!(change)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def deploy_files!(files)
|
|
19
|
+
files.each do |path|
|
|
20
|
+
local_path = remote_path = path
|
|
21
|
+
|
|
22
|
+
if path.is_a?(Hash)
|
|
23
|
+
local_path, remote_path = path.first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
log.debug("Writing file: #{local_path} -> #{remote_path}")
|
|
27
|
+
@adapter.write(remote_path, IO.read(local_path))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def deploy_change!(change)
|
|
34
|
+
case change.type
|
|
35
|
+
when :write
|
|
36
|
+
log.debug("Writing file: #{change.path}")
|
|
37
|
+
@adapter.write(change.path, change.data)
|
|
38
|
+
when :delete
|
|
39
|
+
log.debug("Deleting file: #{change.path}")
|
|
40
|
+
@adapter.delete(change.path)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def exclude?(path)
|
|
45
|
+
excluded = @options[:exclude] || []
|
|
46
|
+
excluded.map { |e| path.start_with?(e) }.any?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def log
|
|
50
|
+
Dandelion.logger
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
module Dandelion
|
|
4
|
+
class Diff
|
|
5
|
+
extend Forwardable
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
def_delegator :@target, :empty?
|
|
9
|
+
def_delegator :@target, :each
|
|
10
|
+
|
|
11
|
+
attr_reader :from_commit, :to_commit
|
|
12
|
+
|
|
13
|
+
def initialize(from_commit, to_commit)
|
|
14
|
+
@from_commit = from_commit
|
|
15
|
+
@to_commit = to_commit
|
|
16
|
+
|
|
17
|
+
if from_commit.nil?
|
|
18
|
+
@target = FullDiff.new(to_commit.diff(nil))
|
|
19
|
+
else
|
|
20
|
+
@target = PartialDiff.new(from_commit.diff(to_commit))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
class PartialDiff
|
|
28
|
+
def initialize(diff)
|
|
29
|
+
@deltas = diff.deltas
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def empty?
|
|
33
|
+
@deltas.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each
|
|
37
|
+
@deltas.each do |delta|
|
|
38
|
+
if delta.deleted?
|
|
39
|
+
yield Change.new(delta.old_file[:path], :delete)
|
|
40
|
+
else
|
|
41
|
+
yield Change.new(delta.new_file[:path], :write)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class FullDiff
|
|
48
|
+
def initialize(diff)
|
|
49
|
+
@deltas = diff.patches.map(&:delta)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def empty?
|
|
53
|
+
@deltas.empty?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def each
|
|
57
|
+
@deltas.each do |delta|
|
|
58
|
+
yield Change.new(delta.new_file[:path], :write)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Dandelion
|
|
2
|
+
class Tree
|
|
3
|
+
attr_reader :commit
|
|
4
|
+
|
|
5
|
+
def initialize(repo, commit)
|
|
6
|
+
@repo = repo
|
|
7
|
+
@commit = commit
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def data(path)
|
|
11
|
+
object = @commit.tree
|
|
12
|
+
|
|
13
|
+
path.split('/').each do |name|
|
|
14
|
+
return nil unless object[name]
|
|
15
|
+
object = @repo.lookup(object[name][:oid])
|
|
16
|
+
return nil unless object
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
object.read_raw.data
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/dandelion/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
module Dandelion
|
|
2
|
-
VERSION = '0.
|
|
3
|
-
end
|
|
2
|
+
VERSION = '0.4.0.beta2'
|
|
3
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Dandelion
|
|
2
|
+
class RevisionError < StandardError; end
|
|
3
|
+
|
|
4
|
+
class Workspace
|
|
5
|
+
attr_reader :adapter, :config
|
|
6
|
+
|
|
7
|
+
def initialize(repo, adapter, config = nil)
|
|
8
|
+
@repo = repo
|
|
9
|
+
@adapter = adapter
|
|
10
|
+
|
|
11
|
+
if config.is_a?(Hash)
|
|
12
|
+
@config = Config.new(data: config)
|
|
13
|
+
else
|
|
14
|
+
@config = config || Config.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@config.defaults(revision_file: '.revision', local_path: '')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tree
|
|
21
|
+
Tree.new(@repo, local_commit)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def changeset
|
|
25
|
+
Changeset.new(tree, remote_commit, @config)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def local_commit
|
|
29
|
+
lookup(local_sha)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remote_commit
|
|
33
|
+
sha = remote_sha
|
|
34
|
+
sha ? lookup(remote_sha) : nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def remote_commit=(commit)
|
|
38
|
+
self.remote_sha = commit.oid
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def lookup(val)
|
|
44
|
+
begin
|
|
45
|
+
begin
|
|
46
|
+
if ref = @repo.ref(val)
|
|
47
|
+
val = ref.target.to_s
|
|
48
|
+
end
|
|
49
|
+
rescue Rugged::ReferenceError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@repo.lookup(val)
|
|
53
|
+
rescue Rugged::OdbError, Rugged::InvalidError
|
|
54
|
+
raise RevisionError.new(val)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def local_sha
|
|
59
|
+
@config[:revision] || @repo.head.target
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def remote_sha
|
|
63
|
+
@remote_sha ||= @adapter.read(@config[:revision_file])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def remote_sha=(sha)
|
|
67
|
+
@adapter.write(@config[:revision_file], sha)
|
|
68
|
+
@remote_sha = sha
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Dandelion::Adapter::Base do
|
|
4
|
+
describe '#create_adapter' do
|
|
5
|
+
it 'registers adapter classes' do
|
|
6
|
+
class TestAdapter < Dandelion::Adapter::Base
|
|
7
|
+
adapter :test
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
expect(Dandelion::Adapter::Base.create_adapter(:test)).to be_a(TestAdapter)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'raises error on invalid adapter' do
|
|
14
|
+
expect {
|
|
15
|
+
Dandelion::Adapter::Base.create_adapter(:another)
|
|
16
|
+
}.to raise_error(Dandelion::Adapter::InvalidAdapterError)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'registers gem list' do
|
|
20
|
+
class TestAdapter < Dandelion::Adapter::Base
|
|
21
|
+
adapter :test
|
|
22
|
+
requires_gems :foo, :bar
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
expect(TestAdapter.required_gems).to eq [:foo, :bar]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'catches load errors' do
|
|
29
|
+
class TestAdapter < Dandelion::Adapter::Base
|
|
30
|
+
adapter :test
|
|
31
|
+
|
|
32
|
+
def initialize(options)
|
|
33
|
+
raise LoadError
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
expect {
|
|
38
|
+
Dandelion::Adapter::Base.create_adapter(:test)
|
|
39
|
+
}.to raise_error(Dandelion::Adapter::MissingDependencyError)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Dandelion::Change do
|
|
4
|
+
let(:change) { Dandelion::Change.new('foo', 'bar', 'baz') }
|
|
5
|
+
|
|
6
|
+
it 'has path' do
|
|
7
|
+
expect(change.path).to eq 'foo'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'has type' do
|
|
11
|
+
expect(change.type).to eq 'bar'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'has data' do
|
|
15
|
+
expect(change.data).to eq 'baz'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Dandelion::Changeset do
|
|
4
|
+
context 'empty local path' do
|
|
5
|
+
let(:changeset) { test_changeset }
|
|
6
|
+
|
|
7
|
+
describe '#enumerable' do
|
|
8
|
+
let(:changes) { changeset.to_a }
|
|
9
|
+
|
|
10
|
+
it 'returns all changes' do
|
|
11
|
+
expect(changes).to be_a(Array)
|
|
12
|
+
expect(changes.length).to eq 5
|
|
13
|
+
expect(changes.map(&:path)).to eq ['bar', 'baz/bar', 'baz/foo', 'foo', 'qux']
|
|
14
|
+
expect(changes.map(&:type)).to eq [:delete, :write, :delete, :write, :write]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'returns data for write changes' do
|
|
18
|
+
expect(changes.select { |c| c.type == :write }.map(&:data)).to eq ["bar\n", "foo\n", ""]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#empty?' do
|
|
23
|
+
it 'returns false' do
|
|
24
|
+
expect(changeset.empty?).to eq false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'non-empty local path' do
|
|
30
|
+
let(:changeset) { test_changeset(local_path: 'baz') }
|
|
31
|
+
|
|
32
|
+
describe '#enumerable' do
|
|
33
|
+
let(:changes) { changeset.to_a }
|
|
34
|
+
|
|
35
|
+
it 'returns all changes' do
|
|
36
|
+
expect(changes).to be_a(Array)
|
|
37
|
+
expect(changes.length).to eq 2
|
|
38
|
+
expect(changes.map(&:path)).to eq ['bar', 'foo']
|
|
39
|
+
expect(changes.map(&:type)).to eq [:write, :delete]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns data for write changes' do
|
|
43
|
+
expect(changes.first.data).to eq "bar\n"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#empty?' do
|
|
48
|
+
it 'returns false' do
|
|
49
|
+
expect(changeset.empty?).to eq false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'empty diff' do
|
|
55
|
+
let(:changeset) { test_changeset }
|
|
56
|
+
before(:each) { changeset.stub(:diff).and_return([]) }
|
|
57
|
+
|
|
58
|
+
describe '#enumerable' do
|
|
59
|
+
let(:changes) { changeset.to_a }
|
|
60
|
+
|
|
61
|
+
it 'returns no changes' do
|
|
62
|
+
expect(changes).to be_a(Array)
|
|
63
|
+
expect(changes.length).to eq 0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '#empty?' do
|
|
68
|
+
it 'returns true' do
|
|
69
|
+
expect(changeset.empty?).to eq true
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|