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 +72 -12
- data/lib/auto_build/association.rb +98 -0
- data/lib/auto_build/builder.rb +68 -13
- data/lib/auto_build/version.rb +1 -1
- data/lib/auto_build.rb +5 -0
- data/test/auto_build_test.rb +21 -0
- data/test/dummy/app/models/nickname.rb +3 -0
- data/test/dummy/app/models/project.rb +2 -0
- data/test/dummy/app/models/user.rb +5 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20111213003425_create_projects.rb +10 -0
- data/test/dummy/db/migrate/20111213033025_create_nicknames.rb +10 -0
- data/test/dummy/db/schema.rb +15 -1
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +229 -0
- data/test/dummy/log/test.log +6183 -0
- data/test/dummy/test/fixtures/nicknames.yml +9 -0
- data/test/dummy/test/fixtures/projects.yml +9 -0
- data/test/dummy/test/unit/nickname_test.rb +7 -0
- data/test/dummy/test/unit/project_test.rb +7 -0
- metadata +23 -6
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
|
10
|
-
with nested fields
|
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 "
|
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`
|
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
|
-
|
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
|
data/lib/auto_build/builder.rb
CHANGED
@@ -6,35 +6,90 @@ module AutoBuild
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module ClassMethods
|
9
|
-
# Public: This method allows you to auto initialize
|
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`
|
12
|
-
# that accept nested attributes for
|
13
|
-
# associations
|
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
|
-
#
|
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
|
-
#
|
25
|
-
#
|
34
|
+
# has_many :pictures
|
35
|
+
# has_many :projects
|
26
36
|
# end
|
27
37
|
#
|
28
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
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
|
data/lib/auto_build/version.rb
CHANGED
data/lib/auto_build.rb
CHANGED
data/test/auto_build_test.rb
CHANGED
@@ -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
|
Binary file
|
data/test/dummy/db/schema.rb
CHANGED
@@ -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 =>
|
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"
|
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|