auto_build 0.0.2 → 0.0.3

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.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