gardener 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.rdoc +80 -24
  2. data/lib/gardener.rb +34 -34
  3. 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 render all the users down into a yaml file, which you could then commit into your source control,
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
- # Dump every instance of a class (every record in the database for a model) into a file
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 "User Salted the earth. Nothing will grow here for some time ..."
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
- - 0
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-08 00:00:00 -05:00
17
+ date: 2011-02-09 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies: []
20
20