auto_build 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,44 +1,104 @@
1
1
  AutoBuild
2
2
  =========
3
3
 
4
- Automatically initialize associations in your Rails models
4
+ Automatically initialize associations in your Rails models.
5
5
 
6
6
  What
7
7
  ----
8
- AutoBuild gives your models the option to automatically initialize (build)
9
- associations. This is useful to skip the `build_association` calls in your controllers when working
10
- with nested fields (`fields_for`).
11
-
12
- Right now it only supports `has_one` associations.
8
+ AutoBuild gives your models the option to automatically initialize (build) their
9
+ associations. This is useful to skip the `build_association` or `record.association.build` calls in
10
+ your models, controllers or views when working with nested fields.
13
11
 
14
12
  Installation
15
13
  ----
16
14
  Add `auto_build` to your Gemfile:
17
15
 
18
- gem "autobuild"
16
+ gem "auto_build"
19
17
 
20
18
  And run `bundle install`.
21
19
 
22
20
  Usage
23
21
  ----
22
+
23
+ ### has_one associations
24
+
24
25
  Just call the `auto_build` method in your models:
25
26
 
26
27
  class User
27
28
  has_one :address
29
+ has_one :phone
30
+
28
31
  auto_build :address
29
32
  end
30
33
 
31
34
  With this in place, `address` will always be initialized, so you don't have to manually call
32
- `build_address` anywhere.
35
+ `build_address` anywhere. If the `User` already has an `address` assigned this won't overwrite it.
36
+
37
+ You can also do:
38
+
39
+ auto_build :address, :phone,
40
+
41
+ To initialize several fields. One `after_initialize` callback will be created per association.
42
+
43
+ ### has_many associations
44
+
45
+ You can automatically initialize a `has_many` association in the same way you initialized a `has_one`
46
+ association:
47
+
48
+ class User
49
+ has_many :friends
50
+ auto_build :friends
51
+ end
52
+
53
+ # User.new.friends.count == 1
54
+
55
+ The behavior will be the same, it will create a new `Friend` if none exists yet. If there's at least
56
+ one `Friend` in the collection then nothing will be created.
57
+
58
+ If you want to always append a new empty object at the end of the collection, you can pass the
59
+ `:append => true` option:
60
+
61
+ class User
62
+ has_many :friends
63
+ auto_build :friends, :append => true
64
+ end
65
+
66
+ # count = some_user.friends.count
67
+ # user = User.find(some_user.id)
68
+ # user.friends.count == count + 1
69
+
70
+ This will always create a new `Friend` instance regardless of the value of `friends`. It
71
+ won't overwrite any existing values.
72
+
73
+ Finally, if you want to always bulk-add a number of records, you can pass the `:count` option with
74
+ the number of records you want to add:
75
+
76
+ class User
77
+ has_many :friends
78
+ auto_build :friends, :count => 5
79
+ end
80
+
81
+ # count = some_user.friends.count
82
+ # user = User.find(some_user.id)
83
+ # user.friends.count == count + 5
84
+
85
+ This will always add 5 **extra** objects at the end of the collection. It won't overwrite any
86
+ existing values.
87
+
88
+ Notes
89
+ ----
90
+ * The option `:append => true` is equivalent to `:count => 5`.
91
+ * You **cannot** pass the `:append` and `:count` options to the same association.
92
+ * None of the operations will overwrite existing objects.
93
+
33
94
 
34
- How
95
+ How it works
35
96
  ----
36
- `auto_build` works by adding an `after_initialize` hook to your model that build the association if
37
- its current value is nil. It **will not** override existing values.
97
+ `auto_build` works by adding an `after_initialize` callback **per association** to your model.
38
98
 
39
99
  Etc.
40
100
  ----
41
- Bugs, feature suggestions, etc. go to the [Issues](https://github.com/febuiles/auto_build/issues)
101
+ To report bugs or suggest new features go to the [Issues](https://github.com/febuiles/auto_build/issues)
42
102
  tracker.
43
103
 
44
104
  This was written by [Federico](http://mheroin.com). You can follow me on Twitter
@@ -0,0 +1,98 @@
1
+ module AutoBuild
2
+ # Public: Represents an ActiveRecord association. Takes care
3
+ # of adding the hooks to the models.
4
+ class Association
5
+ # Public: Returns the model this association works on.
6
+ attr_reader :model
7
+ # Public: Returns the name of the association.
8
+ attr_reader :association_name
9
+ # Public: Returns the options that were passed in when created.
10
+ attr_reader :options
11
+ # Public: Returns the type (macro) of association (e.g. has_one,
12
+ # :has_many).
13
+ attr_reader:type
14
+
15
+ # Public: Creates a new Association.
16
+ #
17
+ # model - The ActiveRecord model (not the instance!).
18
+ # association_name - Name of the association you will want to
19
+ # add the callback to.
20
+ # options - The Hash options used to the number of
21
+ # records (default = {}):
22
+ # :count - An Integer that specifies the number of
23
+ # records to create.
24
+ # :append - Boolean, defines if you only want one record
25
+ # at the end of the array. False by default.
26
+ # Equivalent to :count => 1.
27
+ #
28
+ # `has_one` associations **don't** receive any options.
29
+ #
30
+ # For `has_many` associations, if the user doesn't pass any options,
31
+ # the default will be to add one new record if no other records
32
+ # exist yet.
33
+ #
34
+ # Examples
35
+ #
36
+ # Association.new(User, :photo)
37
+ #
38
+ # Association.new(User, :projects, :append => true)
39
+ #
40
+ # Association.new(User, :projects, :count => 3)
41
+ #
42
+ # Association.new(user, :photo)
43
+ # # => Error! You want to pass the class, no the instance
44
+ #
45
+ # Raises AutoBuildError if you pass the :count and :append options
46
+ # at the same time.
47
+ def initialize(model, association_name, options={})
48
+ @model = model
49
+ @association_name = association_name
50
+ @options = options
51
+ @type = model.reflect_on_association(association_name).macro
52
+
53
+ validate_options
54
+ end
55
+
56
+ # Public: Adds the callback to the objects.
57
+ #
58
+ # It will choose the correct hook based on the value of `type`.
59
+ def add_hook
60
+ if type == :has_one
61
+ has_one_hook
62
+ else
63
+ has_many_hook
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def has_one_hook
70
+ code = "self.#{association_name} ||= build_#{association_name}"
71
+
72
+ model.class_eval do
73
+ after_initialize do |record|
74
+ record.instance_eval(code)
75
+ end
76
+ end
77
+ end
78
+
79
+ def has_many_hook
80
+ name = association_name
81
+ record_options = options
82
+ code = "self.#{association_name}.build;"
83
+
84
+ model.class_eval do
85
+ after_initialize do |record|
86
+ count = number_of_records_to_create(name, record_options)
87
+ record.instance_eval(code * count)
88
+ end
89
+ end
90
+ end
91
+
92
+ def validate_options
93
+ if options[:count] && options[:append]
94
+ raise AutoBuildError.new("You can't specify :count and :append at the same time")
95
+ end
96
+ end
97
+ end
98
+ end
@@ -6,35 +6,90 @@ module AutoBuild
6
6
  end
7
7
 
8
8
  module ClassMethods
9
- # Public: This method allows you to auto initialize `has_one` associations
9
+ # Public: This method allows you to auto initialize associations
10
10
  # in your models. After calling it you don't need to call
11
- # `build_association` in your controllers or views. Useful for models
12
- # that accept nested attributes for associations. It **does not** ovewrite
13
- # associations with existing values.
11
+ # `build_association` or `association.build` in your controllers
12
+ # or views. Useful for models that accept nested attributes for
13
+ # associations. It **does not** overwrite associations with
14
+ # existing values.
14
15
  #
15
16
  # An `after_initialize` hook will be created for each one of
16
17
  # the associations.
17
18
  #
18
- # associations - A symbol or array with the associations you want to build
19
+ # Signature
20
+ #
21
+ # auto_build(<field>, <field>, <field>)
22
+ # auto_build(<field>, <options>)
23
+ #
24
+ # field - A Symbol with the name of the association you want
25
+ # initialize
26
+ # options - A Hash with the options to initialize the
27
+ # associations. See AutoBuilder::Association#initialize
28
+ # for the options.
19
29
  #
20
30
  # Examples
21
31
  #
22
32
  # class User
23
33
  # has_one :address
24
- # has_one :phone
25
- # auto_build :address, :phone
34
+ # has_many :pictures
35
+ # has_many :projects
26
36
  # end
27
37
  #
28
- def auto_build(*associations)
29
- associations = [associations] unless associations.kind_of?(Array)
38
+ # auto_build :address, :pictures
39
+ # # => Builds a record for each association if none is present
40
+ #
41
+ # auto_build :projects, :count => 3
42
+ # # => Builds 3 projects each time you initialize a User
43
+ #
44
+ # auto_build :pictures, :append => true
45
+ # # => Builds an extra Picture each time you initialize a User
46
+ def auto_build(*attr_names)
47
+ options = attr_names.extract_options!
48
+ names = attr_names
30
49
 
31
- associations.each do |assoc|
32
- after_initialize do |record|
33
- record.instance_eval("self.#{assoc} ||= build_#{assoc}")
34
- end
50
+ names.map do |name|
51
+ Association.new(self, name, options).add_hook
35
52
  end
36
53
  end
37
54
  end
55
+
56
+ private
57
+
58
+ # Private: Calculates the number of new records to create in a
59
+ # `has_many` association.
60
+ #
61
+ # name - A Symbol with the name of the association.
62
+ # options - The Hash options for the number of records to create.
63
+ # See AutoBuilder::Association#initialize for more
64
+ # details.
65
+ #
66
+ # Examples
67
+ #
68
+ # User.projects.size
69
+ # # => 1
70
+ #
71
+ # number_of_records_to_create(:projects)
72
+ # # => 0
73
+ #
74
+ # number_of_records_to_create(:projects, :append => true)
75
+ # # => 1
76
+ #
77
+ # number_of_records_to_create(:projects, :count => 4)
78
+ # # => 4 (User.projects.size == 5)
79
+ #
80
+ # Returns the number of records to create.
81
+ def number_of_records_to_create(name, options={})
82
+ current = self.send(name).size
83
+ default_count = current == 0 ? 1 : 0
84
+
85
+ if options[:append]
86
+ 1
87
+ elsif options[:count]
88
+ options[:count]
89
+ else
90
+ default_count
91
+ end
92
+ end
38
93
  end
39
94
  end
40
95
  ActiveRecord::Base.send :include, AutoBuild::Builder
@@ -1,3 +1,3 @@
1
1
  module AutoBuild
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/auto_build.rb CHANGED
@@ -1,4 +1,9 @@
1
1
  require 'auto_build/builder'
2
+ require 'auto_build/association'
3
+ require 'auto_build/has_one_hook'
4
+ require 'auto_build/version'
2
5
 
3
6
  module AutoBuild
7
+ class AutoBuildError < StandardError; end
4
8
  end
9
+
@@ -26,4 +26,25 @@ class AutoBuildTest < ActiveSupport::TestCase
26
26
  assert @user.address
27
27
  assert @user.picture
28
28
  end
29
+
30
+ test "has_many association" do
31
+ assert @user.projects.first
32
+ end
33
+
34
+ test "has_many with :count" do
35
+ assert_equal 3, @user.nicknames.length
36
+ end
37
+
38
+ test "has_many with existing values" do
39
+ @user.save
40
+ assert_equal 1, User.last.projects.size
41
+ end
42
+
43
+ test "has_many with :count and :append raises error" do
44
+ assert_raise AutoBuild::AutoBuildError do
45
+ @user.class_eval do
46
+ auto_build :nicknames, :count => 3, :append => true
47
+ end
48
+ end
49
+ end
29
50
  end
@@ -0,0 +1,3 @@
1
+ class Nickname < ActiveRecord::Base
2
+ belongs_to :user
3
+ end
@@ -0,0 +1,2 @@
1
+ class Project < ActiveRecord::Base
2
+ end
@@ -3,5 +3,10 @@ class User < ActiveRecord::Base
3
3
  has_one :phone
4
4
  has_one :picture
5
5
 
6
+ has_many :projects
7
+ has_many :nicknames
8
+
6
9
  auto_build :address, :picture
10
+ auto_build :projects
11
+ auto_build :nicknames, :count => 3
7
12
  end
Binary file
@@ -0,0 +1,10 @@
1
+ class CreateProjects < ActiveRecord::Migration
2
+ def change
3
+ create_table :projects do |t|
4
+ t.string :title
5
+ t.integer :user_id
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class CreateNicknames < ActiveRecord::Migration
2
+ def change
3
+ create_table :nicknames do |t|
4
+ t.string :nick
5
+ t.integer :user_id
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(:version => 20111212001258) do
14
+ ActiveRecord::Schema.define(:version => 20111213033025) do
15
15
 
16
16
  create_table "addresses", :force => true do |t|
17
17
  t.string "street"
@@ -21,6 +21,13 @@ ActiveRecord::Schema.define(:version => 20111212001258) do
21
21
  t.datetime "updated_at"
22
22
  end
23
23
 
24
+ create_table "nicknames", :force => true do |t|
25
+ t.string "nick"
26
+ t.integer "user_id"
27
+ t.datetime "created_at"
28
+ t.datetime "updated_at"
29
+ end
30
+
24
31
  create_table "phones", :force => true do |t|
25
32
  t.string "number"
26
33
  t.integer "user_id"
@@ -35,6 +42,13 @@ ActiveRecord::Schema.define(:version => 20111212001258) do
35
42
  t.datetime "updated_at"
36
43
  end
37
44
 
45
+ create_table "projects", :force => true do |t|
46
+ t.string "title"
47
+ t.integer "user_id"
48
+ t.datetime "created_at"
49
+ t.datetime "updated_at"
50
+ end
51
+
38
52
  create_table "users", :force => true do |t|
39
53
  t.string "name"
40
54
  t.datetime "created_at"
Binary file