seed-fu 1.2.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ Version 2.0.0 (In Development)
2
+ ------------------------------
3
+
4
+ Features:
5
+
6
+ * Depends only on Active Record, not the whole of Rails
7
+
8
+ * The `Model.seed_many` syntax is now supported by `Model.seed`, and `Model.seed_many` is deprecated
9
+
10
+ * `Model.seed` supports adding multiple records without an explicit array argument. I.e. the following are equivalent:
11
+
12
+ Model.seed([
13
+ { :name => "Jon" },
14
+ { :name => "Emily" }
15
+ ])
16
+
17
+ Model.seed(
18
+ { :name => "Jon" },
19
+ { :name => "Emily }
20
+ )
21
+
22
+ * A side-effect of the above is another option for single seeds:
23
+
24
+ Model.seed(:name => "Jon")
25
+
26
+ * The `SEED` option to `rake db:seed_fu` is deprecated, and replaced by `FILTER` which works the same way.
27
+
28
+ * Added `SeedFu.quiet` boolean option, set to `true` if you don't want any output from Seed Fu.
29
+
30
+ * Added `SeedFu.fixture_paths`. Set to an array of paths to look for seed files in. Defaults to `["db/fixtures"]` in general, or `["#{Rails.root}/db/fixtures", "#{Rails.root}/db/fixtures/#{Rails.env}"]` when Seed Fu is installed as a Rails plugin.
31
+
32
+ * Added `SeedFu.seed` method which is basically a method equivalent of running `rake db:seed_fu` (the rake task now just basically called `SeedFu.seed`)
33
+
34
+ * Simplified and changed the `SeedFu::Writer` API, see docs for details
35
+
36
+ Bug fixes:
37
+
38
+ * Fix Rails 3 deprecation warnings and make seed-fu fully compatible with being installed as a gem
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2010 Michael Bleigh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ Seed Fu
2
+ =======
3
+
4
+ Seed Fu is an attempt to once and for all solve the problem of inserting and maintaining seed data in a database. It uses a variety of techniques gathered from various places around the web and combines them to create what is hopefully the most robust seed data system around.
5
+
6
+ Warning: API Changes
7
+ --------------------
8
+
9
+ Version 2.0.0 of Seed Fu introduces API changes. `Seed::Writer` has been completely overhauled and will require you to update your scripts. Some other deprecations have been introduced, but the methods will not be removed until version 2.1.0. Please see the [CHANGELOG](CHANGELOG.md) for details.
10
+
11
+ The API documentation is available in full at [http://rubydoc.info/github/mbleigh/seed-fu/master/frames](http://rubydoc.info/github/mbleigh/seed-fu/master/frames).
12
+
13
+ Basic Example
14
+ -------------
15
+
16
+ ### In `db/fixtures/users.rb`
17
+
18
+ User.seed do |s|
19
+ s.id = 1
20
+ s.login = "jon"
21
+ s.email = "jon@example.com"
22
+ s.name = "Jon"
23
+ end
24
+
25
+ User.seed do |s|
26
+ s.id = 2
27
+ s.login = "emily"
28
+ s.email = "emily@example.com"
29
+ s.name = "Emily"
30
+ end
31
+
32
+ ### To load the data:
33
+
34
+ $ rake db:seed_fu
35
+ == Seed from /path/to/app/db/fixtures/users.rb
36
+ - User {:id=>1, :login=>"jon", :email=>"jon@example.com", :name=>"Jon"}
37
+ - User {:id=>2, :login=>"emily", :email=>"emily@example.com", :name=>"Emily"}
38
+
39
+ Installation
40
+ ------------
41
+
42
+ ### Rails 3
43
+
44
+ Just add `gem 'seed-fu'` to your `Gemfile`
45
+
46
+ ### Active Record 3
47
+
48
+ Seed Fu depends on Active Record, but doesn't have to be used with a full Rails app. Simply load and require the `seed-fu` gem and you're set.
49
+
50
+ ### Rails 2.3
51
+
52
+ The current version is not backwards compatible with Rails 2.3. Please use Seed Fu [version 1.2.3](https://github.com/mbleigh/seed-fu/tree/v1.2.3).
53
+
54
+ Constraints
55
+ -----------
56
+
57
+ Constraints are used to identify seeds, so that they can be updated if necessary. For example:
58
+
59
+ Point.seed(:x, :y) do |s|
60
+ s.x = 4
61
+ s.y = 7
62
+ s.name = "Home"
63
+ end
64
+
65
+ The first time this seed is loaded, a `Point` record will be created. No suppose the name is changed:
66
+
67
+ Point.seed(:x, :y) do |s|
68
+ s.x = 4
69
+ s.y = 7
70
+ s.name = "Work"
71
+ end
72
+
73
+ When this is run, Seed Fu will look for a `Point` based on the `:x` and `:y` constraints provided. It will see that a matching `Point` already exists and so update its attributes rather than create a new record.
74
+
75
+ If you do not want seeds to be updated after they have been created, use `seed_once`:
76
+
77
+ Point.seed_once(:x, :y) do |s|
78
+ s.x = 4
79
+ s.y = 7
80
+ s.name = "Home"
81
+ end
82
+
83
+ The default constraint just checks the `id` of the record.
84
+
85
+ Where to put seed files
86
+ -----------------------
87
+
88
+ By default, seed files are looked for in the following locations:
89
+
90
+ * `Rails.root/db/fixtures` and `Rails.root/db/fixtures/Rails.env` in a Rails app
91
+ * `db/fixtures` when loaded without Rails
92
+
93
+ You can change these defaults by modifying the `SeedFu.fixture_paths` array.
94
+
95
+ Seed files can be named whatever you like, and are loaded in alphabetical order.
96
+
97
+ Terser syntax
98
+ -------------
99
+
100
+ When loading lots of records, the above block-based syntax can be quite verbose. You can use the following instead:
101
+
102
+ User.seed(:id,
103
+ { :id => 1, :login => "jon", :email => "jon@example.com", :name => "Jon" },
104
+ { :id => 2, :login => "emily", :email => "emily@example.com", :name => "Emily" }
105
+ )
106
+
107
+ Rake task
108
+ ---------
109
+
110
+ Seed files can be run automatically using `rake db:seed_fu`. There are two options which you can pass:
111
+
112
+ * `rake db:seed_fu FIXTURE_PATH=path/to/fixtures` -- Where to find the fixtures
113
+ * `rake db:seed_fu FILTER=users,articles` -- Only run seed files with a filename matching the `FILTER`
114
+
115
+ You can also do a similar thing in your code by calling `SeedFu.seed(fixture_paths, filter)`.
116
+
117
+ Disable output
118
+ --------------
119
+
120
+ To disable output from Seed Fu, set `SeedFu.quiet = true`.
121
+
122
+ Handling large seed files
123
+ -------------------------
124
+
125
+ Seed files can be huge. To handle large files (over a million rows), try these tricks:
126
+
127
+ * Gzip your fixtures. Seed Fu will read .rb.gz files happily. `gzip -9` gives the best compression, and with Seed Fu's repetitive syntax, a 160M file can shrink to 16M.
128
+ * Add lines reading `# BREAK EVAL` in your big fixtures, and Seed Fu will avoid loading the whole file into memory. If you use `SeedFu::Writer`, these breaks are built into your generated fixtures.
129
+ * Load a single fixture at a time with the `FILTER` environment variable
130
+ * If you don't need Seed Fu's ability to update seed with new data, then you may find that [activerecord-import](https://github.com/zdennis/activerecord-import) is faster
131
+
132
+ Generating seed files
133
+ ---------------------
134
+
135
+ If you need to programmatically generate seed files, for example to convert a CSV file into a seed file, then you can use [`SeedFu::Writer`](../SeedFu/Writer).
136
+
137
+ Bugs / Feature requests
138
+ -----------------------
139
+
140
+ Please report them [on Lighthouse](http://mbleigh.lighthouseapp.com/projects/10223-seed-fu).
141
+
142
+ Contributors
143
+ ------------
144
+
145
+ * [Michael Bleigh](http://www.mbleigh.com/) is the original author
146
+ * [Jon Leighton](http://jonathanleighton.com/) is the current maintainer
147
+ * Thanks to [Matthew Beale](https://github.com/mixonic) for his great work in adding the writer, making it faster and better.
148
+
149
+ Copyright © 2008-2010 Michael Bleigh, released under the MIT license
data/lib/seed-fu.rb CHANGED
@@ -1,92 +1,35 @@
1
- module SeedFu
2
- class Seeder
3
- def self.plant(model_class, *constraints, &block)
4
- constraints = [:id] if constraints.empty?
5
- seed = Seeder.new(model_class)
6
- insert_only = constraints.last.is_a? TrueClass
7
- constraints.delete_at(*constraints.length-1) if (constraints.last.is_a? TrueClass or constraints.last.is_a? FalseClass)
8
- seed.set_constraints(*constraints)
9
- yield seed
10
- seed.plant!(insert_only)
11
- end
12
-
13
- def initialize(model_class)
14
- @model_class = model_class
15
- @constraints = [:id]
16
- @data = {}
17
- end
18
-
19
- def set_constraints(*constraints)
20
- raise "You must set at least one constraint." if constraints.empty?
21
- @constraints = []
22
- constraints.each do |constraint|
23
- raise "Your constraint `#{constraint}` is not a column in #{@model_class}. Valid columns are `#{@model_class.column_names.join("`, `")}`." unless @model_class.column_names.include?(constraint.to_s)
24
- @constraints << constraint.to_sym
25
- end
26
- end
27
-
28
- def plant! insert_only=false
29
- record = get
30
- return if !record.new_record? and insert_only
31
- @data.each do |k, v|
32
- record.send("#{k}=", v)
33
- end
34
- raise "Error Saving: #{record.inspect}" unless record.save(false)
35
- puts " - #{@model_class} #{condition_hash.inspect}"
36
- record
37
- end
38
-
39
- def method_missing(method_name, *args) #:nodoc:
40
- if args.size == 1 and (match = method_name.to_s.match(/(.*)=$/))
41
- self.class.class_eval "def #{method_name} arg; @data[:#{match[1]}] = arg; end"
42
- send(method_name, args[0])
43
- else
44
- super
45
- end
46
- end
47
-
48
- protected
49
-
50
- def get
51
- records = @model_class.find(:all, :conditions => condition_hash)
52
- raise "Given constraints yielded multiple records." unless records.size < 2
53
- if records.any?
54
- return records.first
55
- else
56
- return @model_class.new
57
- end
58
- end
1
+ require 'active_record'
2
+ require 'seed-fu/railtie' if defined?(Rails) && Rails.version >= "3"
59
3
 
60
- def condition_hash
61
- @constraints.inject({}) {|a,c| a[c] = @data[c]; a }
62
- end
4
+ module SeedFu
5
+ autoload :VERSION, 'seed-fu/version'
6
+ autoload :Seeder, 'seed-fu/seeder'
7
+ autoload :ActiveRecordExtension, 'seed-fu/active_record_extension'
8
+ autoload :BlockHash, 'seed-fu/block_hash'
9
+ autoload :Runner, 'seed-fu/runner'
10
+ autoload :Writer, 'seed-fu/writer'
11
+
12
+ mattr_accessor :quiet
13
+
14
+ # Set `SeedFu.quiet = true` to silence all output
15
+ @@quiet = false
16
+
17
+ mattr_accessor :fixture_paths
18
+
19
+ # Set this to be an array of paths to directories containing your seed files. If used as a Rails
20
+ # plugin, SeedFu will set to to contain `Rails.root/db/fixtures` and
21
+ # `Rails.root/db/fixtures/Rails.env`
22
+ @@fixture_paths = ['db/fixtures']
23
+
24
+ # Load seed data from files
25
+ # @param [Array] fixture_paths The paths to look for seed files in
26
+ # @param [Regexp] filter If given, only filenames matching this expression will be loaded
27
+ def self.seed(fixture_paths = SeedFu.fixture_paths, filter = nil)
28
+ Runner.new(fixture_paths, filter).run
63
29
  end
64
30
  end
65
31
 
66
-
32
+ # @public
67
33
  class ActiveRecord::Base
68
- # Creates a single record of seed data for use
69
- # with the db:seed rake task.
70
- #
71
- # === Parameters
72
- # constraints :: Immutable reference attributes. Defaults to :id
73
- def self.seed(*constraints, &block)
74
- SeedFu::Seeder.plant(self, *constraints, &block)
75
- end
76
-
77
- def self.seed_once(*constraints, &block)
78
- constraints << true
79
- SeedFu::Seeder.plant(self, *constraints, &block)
80
- end
81
-
82
- def self.seed_many(*constraints)
83
- seeds = constraints.pop
84
- seeds.each do |seed_data|
85
- seed(*constraints) do |s|
86
- seed_data.each_pair do |k,v|
87
- s.send "#{k}=", v
88
- end
89
- end
90
- end
91
- end
34
+ extend SeedFu::ActiveRecordExtension
92
35
  end
@@ -0,0 +1,71 @@
1
+ module SeedFu
2
+ module ActiveRecordExtension
3
+ # Load some seed data. There are two ways to do this.
4
+ #
5
+ # Verbose syntax
6
+ # --------------
7
+ #
8
+ # This will seed a single record. The `:id` parameter ensures that if a record already exists
9
+ # in the database with the same id, then it will be updated with the name and age, rather
10
+ # than created from scratch.
11
+ #
12
+ # Person.seed(:id) do |s|
13
+ # s.id = 1
14
+ # s.name = "Jon"
15
+ # s.age = 21
16
+ # end
17
+ #
18
+ # Note that `:id` is the default attribute used to identify a seed, so it need not be
19
+ # specified.
20
+ #
21
+ # Terse syntax
22
+ # ------------
23
+ #
24
+ # This is a more succinct way to load multiple records. Note that both `:x` and `:y` are being
25
+ # used to identify a seed here.
26
+ #
27
+ # Point.seed(:x, :y,
28
+ # { :x => 3, :y => 10, :name => "Home" },
29
+ # { :x => 5, :y => 9, :name => "Office" }
30
+ # )
31
+ def seed(*args, &block)
32
+ SeedFu::Seeder.new(self, *parse_seed_fu_args(args, block)).seed
33
+ end
34
+
35
+ # Has the same syntax as {#seed}, but if a record already exists with the same values for
36
+ # constraining attributes, it will not be updated.
37
+ #
38
+ # @example
39
+ # Person.seed(:id, :id => 1, :name => "Jon") # => Record created
40
+ # Person.seed(:id, :id => 1, :name => "Bob") # => Name changed
41
+ # Person.seed(:id, :id => 1, :name => "Harry") # => Name *not* changed
42
+ def seed_once(*args, &block)
43
+ constraints, data = parse_seed_fu_args(args, block)
44
+ SeedFu::Seeder.new(self, constraints, data, :insert_only => true).seed
45
+ end
46
+
47
+ # @deprecated Use {#seed} instead
48
+ def seed_many(*args, &block)
49
+ puts "DEPRECATED: Model.seed_many is deprecated. You can now use Model.seed in exactly the same way." unless SeedFu.quiet
50
+ seed(*args, &block)
51
+ end
52
+
53
+ private
54
+
55
+ def parse_seed_fu_args(args, block)
56
+ if block.nil?
57
+ if args.last.is_a?(Array)
58
+ # Last arg is an array of data, so assume the rest of the args are constraints
59
+ data = args.pop
60
+ [args, data]
61
+ else
62
+ # Partition the args, assuming the first hash is the start of the data
63
+ args.partition { |arg| !arg.is_a?(Hash) }
64
+ end
65
+ else
66
+ # We have a block, so assume the args are all constraints
67
+ [args, [SeedFu::BlockHash.new(block).to_hash]]
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ module SeedFu
2
+ # @private
3
+ class BlockHash
4
+ def initialize(proc)
5
+ @hash = {}
6
+ proc.call(self)
7
+ end
8
+
9
+ def to_hash
10
+ @hash
11
+ end
12
+
13
+ def method_missing(method_name, *args, &block)
14
+ if method_name.to_s =~ /^(.*)=$/ && args.length == 1 && block.nil?
15
+ @hash[$1] = args.first
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module SeedFu
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load "tasks/seed_fu.rake"
5
+ end
6
+
7
+ initializer 'seed_fu.set_fixture_paths' do
8
+ SeedFu.fixture_paths = [
9
+ Rails.root.join('db/fixtures').to_s,
10
+ Rails.root.join('db/fixtures/' + Rails.env).to_s
11
+ ]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,73 @@
1
+ require 'zlib'
2
+ require 'active_support/core_ext/array/wrap'
3
+
4
+ module SeedFu
5
+ # Runs seed files.
6
+ #
7
+ # It is not recommended to use this class directly. Instead, use {SeedFu.seed SeedFu.seed}, which creates
8
+ # an instead of {Runner} and calls {#run #run}.
9
+ #
10
+ # @see SeedFu.seed SeedFu.seed
11
+ class Runner
12
+ # @param [Array<String>] fixture_paths The paths where fixtures are located. Will use
13
+ # `SeedFu.fixture_paths` if {nil}. If the argument is not an array, it will be wrapped by one.
14
+ # @param [Regexp] filter If given, only seed files with a file name matching this pattern will
15
+ # be used
16
+ def initialize(fixture_paths = nil, filter = nil)
17
+ @fixture_paths = Array.wrap(fixture_paths || SeedFu.fixture_paths)
18
+ @filter = filter
19
+ end
20
+
21
+ # Run the seed files.
22
+ def run
23
+ puts "\n== Filtering seed files against regexp: #{@filter.inspect}" if @filter && !SeedFu.quiet
24
+
25
+ filenames.each do |filename|
26
+ run_file(filename)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def run_file(filename)
33
+ puts "\n== Seed from #{filename}" unless SeedFu.quiet
34
+
35
+ ActiveRecord::Base.transaction do
36
+ open(filename) do |file|
37
+ chunked_ruby = ''
38
+ file.each_line do |line|
39
+ if line == "# BREAK EVAL\n"
40
+ eval(chunked_ruby)
41
+ chunked_ruby = ''
42
+ else
43
+ chunked_ruby << line
44
+ end
45
+ end
46
+ eval(chunked_ruby) unless chunked_ruby == ''
47
+ end
48
+ end
49
+ end
50
+
51
+ def open(filename)
52
+ if filename[-3..-1] == '.gz'
53
+ Zlib::GzipReader.open(filename) do |file|
54
+ yield file
55
+ end
56
+ else
57
+ File.open(filename) do |file|
58
+ yield file
59
+ end
60
+ end
61
+ end
62
+
63
+ def filenames
64
+ filenames = []
65
+ @fixture_paths.each do |path|
66
+ filenames += (Dir[File.join(path, '*.rb')] + Dir[File.join(path, '*.rb.gz')]).sort
67
+ end
68
+ filenames.uniq!
69
+ filenames = filenames.find_all { |filename| filename =~ @filter } if @filter
70
+ filenames
71
+ end
72
+ end
73
+ end