attribute_defaults 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1 (July 2, 2011)
2
+
3
+ Initial release supporting Rails 3.0 and 3.1.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ attribute_defaults (0.1)
5
+ activerecord (>= 3)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (3.0.9)
11
+ activesupport (= 3.0.9)
12
+ builder (~> 2.1.2)
13
+ i18n (~> 0.5.0)
14
+ activerecord (3.0.9)
15
+ activemodel (= 3.0.9)
16
+ activesupport (= 3.0.9)
17
+ arel (~> 2.0.10)
18
+ tzinfo (~> 0.3.23)
19
+ activesupport (3.0.9)
20
+ arel (2.0.10)
21
+ builder (2.1.2)
22
+ diff-lcs (1.1.2)
23
+ i18n (0.5.0)
24
+ mysql2 (0.2.11)
25
+ rspec (2.6.0)
26
+ rspec-core (~> 2.6.0)
27
+ rspec-expectations (~> 2.6.0)
28
+ rspec-mocks (~> 2.6.0)
29
+ rspec-core (2.6.4)
30
+ rspec-expectations (2.6.0)
31
+ diff-lcs (~> 1.1.2)
32
+ rspec-mocks (2.6.0)
33
+ tzinfo (0.3.29)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ attribute_defaults!
40
+ mysql2 (< 0.3)
41
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jonathan Viney
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,84 @@
1
+ # Active Record Attribute Defaults
2
+
3
+ An easy way to specify default attribute values for new records.
4
+
5
+ ## Quick start
6
+
7
+ class Person < ActiveRecord::Base
8
+ defaults :country => 'New Zealand', :type => 'Unknown', :address => lambda { Address.new }
9
+
10
+ default :last_name do |person|
11
+ person.first_name
12
+ end
13
+ end
14
+
15
+ The default value is only used if the attribute is not present in the attributes hash, so any values you pass in when creating the record will take precedence.
16
+
17
+ Note that the defaults are evaluated when the model class is loaded, so if a default is meant to
18
+ be dynamic, such as today's date, it must be specified as a proc.
19
+
20
+ class Person < ActiveRecord::Base
21
+ default :today => Date.today # WRONG
22
+ default :today => lambda { Date.today } # RIGHT
23
+ end
24
+
25
+ Interestingly, because the model classes are reloaded for every request in development mode,
26
+ the first default would always work as expected. But in production, where the model classes are
27
+ only loaded once, the date will shortly become incorrect.
28
+
29
+ ## More information
30
+
31
+ Use this gem to define default values for attributes on new records.
32
+ Requires a hash of `attribute => value` pairs, or a single attribute with an associated block.
33
+
34
+ * If the value is a block, it will be called to retrieve the default value.
35
+ * If the value is a symbol, a method by that name will be called on the object to retrieve the default value.
36
+
37
+ The following code demonstrates the different ways default values can be specified. Defaults are applied in the order they are defined.
38
+
39
+ class Person < ActiveRecord::Base
40
+ defaults :name => "My name", :city => lambda { "My city" }
41
+
42
+ default :birthdate do |person|
43
+ Date.current if person.wants_birthday_today?
44
+ end
45
+
46
+ default :favourite_colour => :default_favourite_colour
47
+
48
+ def default_favourite_colour
49
+ "Blue"
50
+ end
51
+ end
52
+
53
+ The `defaults` and the `default` methods behave the same way. Use whichever is appropriate.
54
+
55
+ The default values are only used if the key is not present in the given attributes.
56
+ Therefore, the above code will behave in the following way:
57
+
58
+ p = Person.new
59
+ p.name # "My name"
60
+ p.city # "My city"
61
+
62
+ p = Person.new(:name => nil)
63
+ p.name # nil
64
+ p.city # "My city"
65
+
66
+ ### Default values for belongs_to associations
67
+
68
+ Default values can also be specified for an association. For instance:
69
+
70
+ class Student < ActiveRecord::Base
71
+ belongs_to :school
72
+
73
+ default :school => lambda { School.favourite }
74
+ end
75
+
76
+ In this scenario, if a `school_id` was provided in the attributes hash, the default value for the association will be ignored:
77
+
78
+ s = Student.new
79
+ s.school # => #<School: ...>
80
+
81
+ s = Student.new(:school_id => nil)
82
+ s.school # => nil
83
+
84
+ Similarly, if a default value is specified for the foreign key and an object for the association is provided, the default foreign key is ignored.
@@ -0,0 +1,17 @@
1
+ require File.expand_path("../lib/active_record/attribute_defaults/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "attribute_defaults"
5
+ s.version = ActiveRecord::AttributeDefaults::VERSION
6
+ s.summary = "attribute_defaults-#{s.version}"
7
+ s.description = "Add default attribute values when creating models."
8
+ s.authors = ["Jonathan Viney"]
9
+ s.email = "jonathan.viney@gmail.com"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.homepage = "http://github.com/jviney/attribute_defaults"
12
+
13
+ s.add_dependency "activerecord", ">= 3"
14
+
15
+ s.add_development_dependency "rspec"
16
+ s.add_development_dependency "mysql2", "< 0.3"
17
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "active_record/attribute_defaults"
@@ -0,0 +1,138 @@
1
+ require "active_support/concern"
2
+ require "active_support/core_ext/module/aliasing"
3
+ require "active_support/core_ext/class/attribute"
4
+
5
+ require "active_record/attribute_defaults/default"
6
+
7
+ module ActiveRecord
8
+ module AttributeDefaults
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ alias_method_chain :initialize, :defaults
13
+
14
+ class_attribute :attribute_defaults
15
+ self.attribute_defaults = []
16
+ end
17
+
18
+ module ClassMethods
19
+ # Define default values for attributes on new records. Requires a hash of <tt>attribute => value</tt> pairs, or a single attribute with an associated block.
20
+ # If the value is a block, it will be called to retrieve the default value.
21
+ # If the value is a symbol, a method by that name will be called on the object to retrieve the default value.
22
+ #
23
+ # The following code demonstrates the different ways default values can be specified. Defaults are applied in the order they are defined.
24
+ #
25
+ # class Person < ActiveRecord::Base
26
+ # defaults :name => 'My name', :city => lambda { 'My city' }
27
+ #
28
+ # default :birthdate do |person|
29
+ # Date.today if person.wants_birthday_today?
30
+ # end
31
+ #
32
+ # default :favourite_colour => :default_favourite_colour
33
+ #
34
+ # def default_favourite_colour
35
+ # "Blue"
36
+ # end
37
+ # end
38
+ #
39
+ # The <tt>defaults</tt> and the <tt>default</tt> methods behave the same way. Use whichever is appropriate.
40
+ #
41
+ # The default values are only used if the key is not present in the given attributes.
42
+ #
43
+ # p = Person.new
44
+ # p.name # "My name"
45
+ # p.city # "My city"
46
+ #
47
+ # p = Person.new(:name => nil)
48
+ # p.name # nil
49
+ # p.city # "My city"
50
+ #
51
+ # == Default values for belongs_to associations
52
+ #
53
+ # Default values can also be specified for an association. For instance:
54
+ #
55
+ # class Student < ActiveRecord::Base
56
+ # belongs_to :school
57
+ # default :school => lambda { School.new }
58
+ # end
59
+ #
60
+ # In this scenario, if a school_id was provided in the attributes hash, the default value for the association will be ignored:
61
+ #
62
+ # s = Student.new
63
+ # s.school # => #<School: ...>
64
+ #
65
+ # s = Student.new(:school_id => nil)
66
+ # s.school # => nil
67
+ #
68
+ # Similarly, if a default value is specified for the foreign key and an object for the association is provided, the default foreign key is ignored.
69
+ def defaults(defaults, &block)
70
+ default_objects = case
71
+ when defaults.is_a?(Hash)
72
+ defaults.map { |attribute, value| Default.new(attribute, value) }
73
+
74
+ when defaults.is_a?(Symbol) && block
75
+ Default.new(defaults, block)
76
+
77
+ else
78
+ raise "pass either a hash of attribute/value pairs, or a single attribute with a block"
79
+ end
80
+
81
+ self.attribute_defaults += Array.wrap(default_objects)
82
+ end
83
+
84
+ alias_method :default, :defaults
85
+ end
86
+
87
+ module InstanceMethods
88
+ if ActiveRecord::VERSION::STRING >= "3.1"
89
+ def initialize_with_defaults(attributes = nil, options = {})
90
+ initialize_without_defaults(attributes, options) do |record|
91
+ record.apply_default_attribute_values(attributes)
92
+ yield record if block_given?
93
+ end
94
+ end
95
+ else
96
+ def initialize_with_defaults(attributes = nil)
97
+ initialize_without_defaults(attributes) do |record|
98
+ record.apply_default_attribute_values(attributes)
99
+ yield record if block_given?
100
+ end
101
+ end
102
+ end
103
+
104
+ def apply_default_attribute_values(specific_attributes)
105
+ specific_attributes = (specific_attributes || {}).stringify_keys
106
+
107
+ # Rails 3.1 deprecates #primary_key_name in favour of :foreign_key
108
+ foreign_key_method = if ActiveRecord::VERSION::STRING >= "3.1"
109
+ :foreign_key
110
+ else
111
+ :primary_key_name
112
+ end
113
+
114
+ self.class.attribute_defaults.each do |default|
115
+ next if specific_attributes.include?(default.attribute)
116
+
117
+ # Ignore a default value for association_id if association has been specified
118
+ reflection = self.class.reflections[default.attribute.to_sym]
119
+ if reflection and reflection.macro == :belongs_to and specific_attributes.include?(reflection.send(foreign_key_method).to_s)
120
+ next
121
+ end
122
+
123
+ # Ignore a default value for association if association_id has been specified
124
+ reflection = self.class.reflections.values.find { |r| r.macro == :belongs_to && r.send(foreign_key_method).to_s == default.attribute }
125
+ if reflection and specific_attributes.include?(reflection.name.to_s)
126
+ next
127
+ end
128
+
129
+ send("#{default.attribute}=", default.value(self))
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ class ActiveRecord::Base
137
+ include ActiveRecord::AttributeDefaults
138
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord
2
+ module AttributeDefaults
3
+ class Default
4
+ attr_reader :attribute
5
+
6
+ def initialize(attribute, value)
7
+ @attribute, @value = attribute.to_s, value
8
+ end
9
+
10
+ def value(record)
11
+ if @value.is_a?(Symbol)
12
+ record.send(@value)
13
+ elsif @value.respond_to?(:call)
14
+ @value.call(record)
15
+ else
16
+ @value.duplicable? ? @value.dup : @value
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module AttributeDefaults
3
+ VERSION = "0.1"
4
+ end
5
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe "active_record/attribute_defaults" do
4
+ it "should supply defaults for a new record" do
5
+ p = Person.new
6
+
7
+ p.city.should == "Christchurch"
8
+ p.country.should == "New Zealand"
9
+ p.first_name.should == "Sean"
10
+ p.last_name.should == "Fitzpatrick"
11
+ p.lucky_number.should == 2
12
+ p.favourite_colour.should == "Blue"
13
+ end
14
+
15
+ it "should ignore the default if a specific value is given" do
16
+ p = Person.new(:city => "", "lucky_number" => nil)
17
+
18
+ p.city.should == ""
19
+ p.lucky_number.should be_nil
20
+
21
+ p.country.should == "New Zealand"
22
+ p.first_name.should == "Sean"
23
+ p.last_name.should == "Fitzpatrick"
24
+ p.favourite_colour.should == "Blue"
25
+ end
26
+
27
+ it "should not cache the result of the default block" do
28
+ one = Person.new
29
+ two = Person.new
30
+
31
+ one.first_name.object_id.should_not == two.first_name.object_id
32
+ end
33
+
34
+ it "should not provide defaults for existing records" do
35
+ existing_person = Person.create!(:last_name => "Key")
36
+ Person.find(existing_person.id).last_name.should == "Key"
37
+ end
38
+
39
+ it "should process the defaults in the order of definition" do
40
+ Person.new(:last_name => "Carter").favourite_colour.should == "Red"
41
+ end
42
+
43
+ it "should add defaults on create!" do
44
+ p = Person.create!
45
+
46
+ p.city.should == "Christchurch"
47
+ p.country.should == "New Zealand"
48
+ p.first_name.should == "Sean"
49
+ p.last_name.should == "Fitzpatrick"
50
+ p.lucky_number.should == 2
51
+ p.favourite_colour.should == "Blue"
52
+ end
53
+
54
+ it "should allow a default object for a belongs_to association" do
55
+ @school = School.create! { |s| s.id = 1 }
56
+ PersonWithDefaultSchool.new.school.should == @school
57
+ @school.destroy
58
+ end
59
+
60
+ it "should not use the default for a belongs to association when a nil id is supplied" do
61
+ PersonWithDefaultSchool.new(:school_id => nil).school.should be_nil
62
+ end
63
+
64
+ it "should allow a default id for a belongs_to association" do
65
+ @school = School.create! { |s| s.id = 1 }
66
+ PersonWithDefaultSchoolId.new.school.should == @school
67
+ @school.destroy
68
+ end
69
+
70
+ it "should ignore a default id for a belongs_to association when an object is supplied" do
71
+ PersonWithDefaultSchoolId.new(:school => nil).school.should be_nil
72
+ end
73
+
74
+ it "should not raise an error when no defaults are defined" do
75
+ klass = Class.new(ActiveRecord::Base) { set_table_name "people" }
76
+ lambda { klass.new }.should_not raise_error
77
+ end
78
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,7 @@
1
+ mysql:
2
+ :adapter: mysql2
3
+ :host: localhost
4
+ :username: rails
5
+ :password:
6
+ :database: rails_plugin_test
7
+ :socket: /tmp/mysql.sock
data/spec/schema.rb ADDED
@@ -0,0 +1,18 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table :people, :force => true do |t|
3
+ t.column :first_name, :string
4
+ t.column :middle_name, :string
5
+ t.column :last_name, :string
6
+ t.column :city, :string
7
+ t.column :country, :string
8
+ t.column :birthdate, :date
9
+ t.column :lucky_number, :integer
10
+ t.column :favourite_colour, :string
11
+ t.column :type, :string
12
+ t.column :school_id, :integer
13
+ end
14
+
15
+ create_table :schools, :force => true do |t|
16
+ t.column :name, :string
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "rspec"
4
+
5
+ require "active_record"
6
+ require "active_record/base"
7
+
8
+ require File.expand_path("../../lib/active_record/attribute_defaults", __FILE__)
9
+
10
+ ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + "/database.yml"))
11
+ ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FILE__) + "/debug.log")
12
+ ActiveRecord::Base.establish_connection(ENV["DB"] || "mysql")
13
+
14
+ load(File.dirname(__FILE__) + "/schema.rb")
15
+
16
+ # Fixtures
17
+ Address = Struct.new(:suburb, :city)
18
+
19
+ class Group < ActiveRecord::Base
20
+ end
21
+
22
+ class Person < ActiveRecord::Base
23
+ belongs_to :school
24
+
25
+ # Include an aggregate reflection to check compatibility
26
+ composed_of :address, :mapping => [%w(address_suburb suburb), %(address_city city)]
27
+
28
+ defaults :city => "Christchurch", :country => lambda { "New Zealand" }
29
+
30
+ default :first_name => "Sean"
31
+
32
+ default :last_name do
33
+ "Fitzpatrick"
34
+ end
35
+
36
+ defaults :lucky_number => lambda { 2 }, :favourite_colour => :default_favourite_colour
37
+
38
+ def default_favourite_colour
39
+ last_name == "Fitzpatrick" ? "Blue" : "Red"
40
+ end
41
+ end
42
+
43
+ class PersonWithDefaultSchool < Person
44
+ default :school do
45
+ School.find(1)
46
+ end
47
+ end
48
+
49
+ class PersonWithDefaultSchoolId < Person
50
+ default :school_id => 1
51
+ end
52
+
53
+ class School < ActiveRecord::Base
54
+ has_many :people
55
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attribute_defaults
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Jonathan Viney
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-07-02 00:00:00 +12:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 5
29
+ segments:
30
+ - 3
31
+ version: "3"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: mysql2
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - <
55
+ - !ruby/object:Gem::Version
56
+ hash: 13
57
+ segments:
58
+ - 0
59
+ - 3
60
+ version: "0.3"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: Add default attribute values when creating models.
64
+ email: jonathan.viney@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files: []
70
+
71
+ files:
72
+ - Changelog.md
73
+ - Gemfile
74
+ - Gemfile.lock
75
+ - LICENSE
76
+ - Readme.md
77
+ - attribute_defaults.gemspec
78
+ - init.rb
79
+ - lib/active_record/attribute_defaults.rb
80
+ - lib/active_record/attribute_defaults/default.rb
81
+ - lib/active_record/attribute_defaults/version.rb
82
+ - spec/active_record/attribute_defaults_spec.rb
83
+ - spec/database.yml
84
+ - spec/schema.rb
85
+ - spec/spec_helper.rb
86
+ has_rdoc: true
87
+ homepage: http://github.com/jviney/attribute_defaults
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options: []
92
+
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ requirements: []
114
+
115
+ rubyforge_project:
116
+ rubygems_version: 1.6.2
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: attribute_defaults-0.1
120
+ test_files: []
121
+