gardener 1.0.0 → 1.1.0
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/README.rdoc +80 -24
- data/lib/gardener.rb +34 -34
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -1,44 +1,100 @@
|
|
1
1
|
=Gardener
|
2
2
|
==A Simple Rails Plugin to help you create seed data
|
3
|
-
Easy transfer of individual records in a Rails app between environments via yaml dumps.
|
4
|
-
|
5
|
-
But you don't need to know that.
|
6
|
-
|
7
|
-
All you care about is
|
3
|
+
Easy transfer of individual records in a Rails app between environments via yaml dumps.
|
4
|
+
Gardener is intended to be used from the rails console, although I suppose you could script it with rake or something if you really wanted to.
|
8
5
|
|
9
6
|
==Usage
|
10
|
-
Put
|
7
|
+
Put
|
11
8
|
gem 'gardener'
|
12
9
|
in your gemfile and gardener gets right to work.
|
13
10
|
|
14
11
|
He's there behind the scenes and you can call on him in several ways.
|
15
12
|
|
13
|
+
==Instance Methods
|
14
|
+
|
15
|
+
===plant_seed
|
16
|
+
c = Content.first
|
17
|
+
c.plant_seed
|
18
|
+
|
19
|
+
This is the use case I wrote Gardener to solve.
|
20
|
+
I found myself (any my designers) making lots of fiddly copy changes to databased content, and needed a simple way to
|
21
|
+
pick and choose individual records to move from one environment (development --> staging or staging --> production) to another.
|
22
|
+
If you're asking yourself, "Why do you need to move just a few records from one database to another?" then you are lucky and I wish I was you.
|
23
|
+
If you're saying "Oh yeah, I do that too, and boy is it a PITA" then you are me and we are very lucky too because Gardener is here to help us out.
|
24
|
+
|
25
|
+
Gardener should be pretty much database agnostic, if you can .to_yaml it, Gardener can work in whatever soil type you have.
|
26
|
+
|
27
|
+
plant_seed can plant seeds for associated objects (or any method that returns objects).
|
28
|
+
c.plant_seed(:include => :foos)
|
29
|
+
# plant seeds for foo objects associated with c
|
30
|
+
c.plant_seed(:include => [:foos, :bars])
|
31
|
+
# plant seeds for foo and bar objects associated with c
|
32
|
+
c.plant_seed(:include => {:foos => :bazs})
|
33
|
+
# plant seeds for foo objects associated with c, and plant seeds for baz objects associated with those foo objects
|
34
|
+
|
35
|
+
n.b. There is not currently a way to reconstitute associated objects in the same call as the object itself.
|
36
|
+
To rebuild the associations in the example above you would call Content.reap, Foo.reap and Bar.reap.
|
37
|
+
So far this isn't a major pain point for me but it is on the list of things to do.
|
38
|
+
|
39
|
+
|
40
|
+
If you need to copy entire tables or databases from one thing to another Taps
|
41
|
+
http://adam.heroku.com/past/2009/2/11/taps_for_easy_database_transfers/ is probably a better bet for you.
|
42
|
+
|
43
|
+
===diff
|
44
|
+
As a helper method for determining what attributes of an object conflict there is a diff method available to you.
|
45
|
+
u = User.first
|
46
|
+
uu = User.last
|
47
|
+
u.diff(uu)
|
48
|
+
# {"encrypted_password"=>
|
49
|
+
# {:original=>"c90196966e062a2e62fa7b42e3e0bbe8b529600b",
|
50
|
+
# :new=>"13630bfb3bb60320170968bac76b7d6f6beebca5"},
|
51
|
+
# "salt"=>
|
52
|
+
# {:original=>"e9dc65d4492d2deee519bb265d972e6054ccc254",
|
53
|
+
# :new=>"600d7de1ac258ea3df5e833e3717120b577d9031"},
|
54
|
+
# "created_at"=>
|
55
|
+
# {:original=>Tue Aug 11 13:55:27 UTC 2009,
|
56
|
+
# :new=>Tue, 11 May 2010 12:28:44 CDT -05:00},
|
57
|
+
# "token"=>{:original=>nil, :new=>"cebd2d210d6e9072d3cc7d46f98d64df6d4b8ce5"},
|
58
|
+
# "updated_at"=>
|
59
|
+
# {:original=>Tue Sep 21 13:41:00 UTC 2010,
|
60
|
+
# :new=>Tue, 11 May 2010 12:35:38 CDT -05:00},
|
61
|
+
# "id"=>{:original=>1, :new=>13},
|
62
|
+
# "token_expires_at"=>
|
63
|
+
# {:original=>nil, :new=>Tue, 25 May 2010 12:35:38 CDT -05:00}}
|
64
|
+
|
65
|
+
You will encounter this if you have conflicting IDs during a reaping cycle, and it's there for you to use for any other reason that strikes your fancy.
|
66
|
+
|
67
|
+
|
68
|
+
|
16
69
|
==Class Methods
|
17
70
|
|
71
|
+
===reap
|
72
|
+
"[...] whatsoever a man soweth, that shall he also reap" - Galatians 6:7-8
|
73
|
+
For reaping the fruits of your labor.
|
74
|
+
|
75
|
+
This is how you get records out of your garden and back into your database.
|
76
|
+
|
77
|
+
It is not unusual when transfering a couple records from one database to another to discover the IDs are already taken.
|
78
|
+
When Gardener determines you are trying to sprout a record with an ID that already exists, it will prompt you to take one of several actions.
|
79
|
+
|
80
|
+
'y' will overwrite the database record with the attributes of the garden record.
|
81
|
+
'n' will make no changes to the database record.
|
82
|
+
'i' will give the garden record a new id, so that it doesn't conflict anymore. Database record stays the same, garden record gets a new identity.
|
83
|
+
'a' will raise an error and abort the whole procedure, making no changes to anything. This is your failsafe for when you realize you weren't done sowing your wild oats.
|
84
|
+
|
85
|
+
You can add 'z' to any of the above responses and Gardener will apply that response to the remainder of the records being reaped. Most often used like 'iz'.
|
86
|
+
|
87
|
+
|
18
88
|
===sow
|
19
|
-
User.sow
|
20
|
-
for example, would
|
21
|
-
and deploy to staging or production.
|
89
|
+
User.sow
|
90
|
+
for example, would plant all the User records in the garden, which you could then commit into your (D)VCS and deploy wherever.
|
22
91
|
|
23
92
|
Once deployed you could write a rake task :P
|
24
|
-
or just go into the rails console :D
|
25
|
-
and call
|
93
|
+
or just go into the rails console :D
|
94
|
+
and call
|
26
95
|
User.reap
|
27
96
|
to pull the records up out of the file and into your database.
|
28
|
-
===reap
|
29
|
-
"[...] whatsoever a man soweth, that shall he also reap" - Galatians 6:7-8
|
30
|
-
For reaping the fruits of your labor.
|
31
97
|
|
32
|
-
==Instance Methods
|
33
|
-
|
34
|
-
===plant_seed
|
35
|
-
User.find(1)
|
36
|
-
User.plant_seed
|
37
|
-
Would put only the one user in a file to be reconstituted later. This is the use case that I wrote this for,
|
38
|
-
I found myself making lots of copy changes to databased content for websites, and needed a simple way to move those things
|
39
|
-
from one environment (development --> staging or staging --> production) to another. I didn't want to copy over entire tables
|
40
|
-
because there were some records that didn't need to come over, so existing tools like Taps http://adam.heroku.com/past/2009/2/11/taps_for_easy_database_transfers/
|
41
|
-
weren't a perfect fit for me.
|
42
98
|
|
43
99
|
|
44
100
|
|
data/lib/gardener.rb
CHANGED
@@ -2,24 +2,24 @@ module Gardener
|
|
2
2
|
def self.included(base)
|
3
3
|
base.extend(ClassMethods)
|
4
4
|
end
|
5
|
-
|
5
|
+
|
6
6
|
module ClassMethods
|
7
|
-
|
7
|
+
|
8
8
|
attr_accessor :infinite_choice
|
9
|
-
|
10
9
|
|
11
|
-
|
10
|
+
|
11
|
+
# Dump every instance of a class (every record in the database for a model) into a file
|
12
12
|
# to be reconstituted later.
|
13
13
|
def sow
|
14
14
|
c = self.to_s.underscore.pluralize
|
15
15
|
dir = File.join [Rails.root, 'db', 'garden']
|
16
16
|
Dir.mkdir(dir) unless File.exists?(dir)
|
17
17
|
|
18
|
-
File.open(File.join([Rails.root, 'db', 'garden', "#{c}.yml"] ), 'w') do |file|
|
18
|
+
File.open(File.join([Rails.root, 'db', 'garden', "#{c}.yml"] ), 'w') do |file|
|
19
19
|
self.find_each{ |x| file << x.to_yaml }
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
|
24
24
|
# Take the things that were dumped to a file and put them in the database.
|
25
25
|
def reap
|
@@ -34,31 +34,31 @@ module Gardener
|
|
34
34
|
end
|
35
35
|
sprout(str) unless str.blank?
|
36
36
|
end
|
37
|
-
|
38
|
-
|
37
|
+
|
38
|
+
|
39
39
|
# Helper function to reconstitute a record from it's yaml representation.
|
40
40
|
def sprout(str)
|
41
41
|
return false if str.blank?
|
42
42
|
# puts str.inspect
|
43
|
-
|
43
|
+
|
44
44
|
foo = YAML.load(str)
|
45
|
-
return unless foo # YAML.load('') == false
|
45
|
+
return unless foo # YAML.load('') == false
|
46
46
|
bob = self.new(foo.attributes)
|
47
47
|
bob.id = foo.id
|
48
48
|
|
49
49
|
# Grr, have to check to see if anything in the inheritance hierarchy has the id.
|
50
|
-
base_class = self.base_class
|
50
|
+
base_class = self.base_class
|
51
51
|
scott = base_class.find_by_id(bob.id)
|
52
|
-
|
52
|
+
|
53
53
|
if scott
|
54
|
-
puts "\n\n#{bob.class.name} already exists, differences are"
|
54
|
+
puts "\n\n#{bob.class.name} with id #{bob.id} already exists, differences are"
|
55
55
|
bar = scott.diff(bob)
|
56
56
|
if bar.blank?
|
57
57
|
puts "\tActually, there are no differences, even the ids are the same #{bob.id} == #{scott.id}.\n\tLooks like you're trying to bring in the exact same object that already exists in the db."
|
58
58
|
else
|
59
59
|
pp bar
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
if @infinite_choice
|
63
63
|
res = @infinite_choice
|
64
64
|
else
|
@@ -66,17 +66,17 @@ module Gardener
|
|
66
66
|
res = gets
|
67
67
|
@infinite_choice = res if res.match(/z/i)
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
if res.match(/y/i)
|
71
71
|
scott = bob
|
72
72
|
scott.save
|
73
73
|
elsif res.match(/a/i)
|
74
|
-
raise "
|
74
|
+
raise "Salting the earth. Nothing will grow here for some time ..."
|
75
75
|
elsif res.match(/i/i)
|
76
76
|
bob.id = nil
|
77
77
|
bob.save
|
78
78
|
else
|
79
|
-
puts "#{bob.class.name} with id #{bob.id} already exists, doing nothing."
|
79
|
+
puts "#{bob.class.name} with id #{bob.id} already exists, doing nothing."
|
80
80
|
end
|
81
81
|
else
|
82
82
|
bob.save
|
@@ -97,45 +97,45 @@ module Gardener
|
|
97
97
|
if tmp.eql?(v)
|
98
98
|
# they're the same, do nothing
|
99
99
|
else
|
100
|
-
buff[k] = {:original => v, :new => tmp}
|
100
|
+
buff[k] = {:original => v, :new => tmp}
|
101
101
|
end
|
102
102
|
end # end self.attributes.each
|
103
103
|
buff
|
104
104
|
end
|
105
105
|
|
106
106
|
|
107
|
-
# Called on an particular instance of a class to render it down into a file, from which it can be
|
107
|
+
# Called on an particular instance of a class to render it down into a file, from which it can be
|
108
108
|
# _reaped_ later.
|
109
109
|
def plant_seed(options = {})
|
110
110
|
c = self.class.to_s.underscore.pluralize
|
111
111
|
# Code to render down associated items exists, but nothing has been done to pull those objects back out.
|
112
|
-
# Not automatically at least, but of course reaping the correct model will pull them up no problem.
|
113
|
-
|
114
|
-
|
112
|
+
# Not automatically at least, but of course reaping the correct model will pull them up no problem.
|
113
|
+
|
114
|
+
|
115
115
|
# TODO
|
116
116
|
# need to load the file up and see if the thing i'm trying to add is in it
|
117
117
|
# or just overwrite the whole file every time.
|
118
|
-
# Do something to prevent duplicates from slipping in.
|
118
|
+
# Do something to prevent duplicates from slipping in.
|
119
119
|
dir = File.join [Rails.root, 'db', 'garden']
|
120
120
|
Dir.mkdir(dir) unless File.exists?(dir)
|
121
|
-
|
122
|
-
|
121
|
+
|
122
|
+
|
123
123
|
File.open(File.join([Rails.root, 'db', 'garden', "#{c}.yml"] ), 'a') do |file|
|
124
124
|
file << self.to_yaml
|
125
|
-
end
|
126
|
-
|
125
|
+
end
|
126
|
+
|
127
127
|
includes = things_to_include(options)
|
128
128
|
puts includes
|
129
129
|
includes.each do |k, v|
|
130
130
|
tom = self.send(k) if self.respond_to?(k)
|
131
131
|
[*tom].each{|y| y.plant_seed({:include => v})}
|
132
|
-
end
|
132
|
+
end
|
133
133
|
|
134
134
|
end
|
135
|
-
|
136
|
-
|
135
|
+
|
136
|
+
|
137
137
|
def things_to_include(options)
|
138
|
-
# [*THING] is so that THING can be an array or a single instance of a class.
|
138
|
+
# [*THING] is so that THING can be an array or a single instance of a class.
|
139
139
|
# .each will blow up if THING is just an instance, not an array, unless we explode and re-array it.
|
140
140
|
return [] unless options[:include]
|
141
141
|
case options[:include]
|
@@ -150,8 +150,8 @@ module Gardener
|
|
150
150
|
{"#{self.class.to_s.underscore}_#{self.id}" => self.attributes}.to_yaml
|
151
151
|
end
|
152
152
|
|
153
|
-
|
154
|
-
|
153
|
+
|
154
|
+
|
155
155
|
end
|
156
156
|
|
157
|
-
ActiveRecord::Base.class_eval{ include Gardener }
|
157
|
+
ActiveRecord::Base.class_eval{ include Gardener }
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 1.0.0
|
9
|
+
version: 1.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Bob Larrick
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-02-
|
17
|
+
date: 2011-02-09 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|