default_value_for-3.4.0/0000755000004100000410000000000014022061274015232 5ustar www-datawww-datadefault_value_for-3.4.0/README.md0000644000004100000410000003724714022061274016526 0ustar www-datawww-data# Introduction The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner. For example: ```ruby class User < ActiveRecord::Base default_value_for :name, "(no name)" default_value_for :last_seen do Time.now end end u = User.new u.name # => "(no name)" u.last_seen # => Mon Sep 22 17:28:38 +0200 2008 ``` *Note*: critics might be interested in the "When (not) to use default_value_for?" section. Please read on. ## Installation ### Rails 3.2 - 4.2 / Ruby 1.9.3 and higher The current version of default_value_for (3.x+) is compatible with Rails 3.2 or higher, and Ruby 1.9.3 and higher. Add it to your Gemfile: ```ruby gem "default_value_for", "~> 3.0" ``` This gem is signed using PGP with the Phusion Software Signing key: http://www.phusion.nl/about/gpg. That key in turn is signed by the rubygems-openpgp Certificate Authority: http://www.rubygems-openpgp-ca.org/. You can verify the authenticity of the gem by following The Complete Guide to Verifying Gems with rubygems-openpgp: http://www.rubygems-openpgp-ca.org/blog/the-complete-guide-to-verifying-gems-with-rubygems-openpgp.html ## Rails 3.0 - 3.1 / Ruby 1.9.3 and lower To use default_value_for with older versions of Ruby and Rails, you must use the previous stable release, 2.0.3. This version works with Rails 3.0, 3.1, and 3.2; and Ruby 1.8.7 and higher. It **does not** work with Rails 4. ```ruby gem "default_value_for", "~> 2.0.3" ``` ### Rails 2 To use default_value_for with Rails 2.x you must use an older version: ```shell ./script/plugin install git://github.com/FooBarWidget/default_value_for.git -r release-1.0.7 ``` ## The default_value_for method The `default_value_for` method is available in all ActiveRecord model classes. The first argument is the name of the attribute for which a default value should be set. This may either be a Symbol or a String. The default value itself may either be passed as the second argument: ```ruby default_value_for :age, 20 ``` ...or it may be passed as the return value of a block: ```ruby default_value_for :age do if today_is_sunday? 20 else 30 end end ``` If you pass a value argument, then the default value is static and never changes. However, if you pass a block, then the default value is retrieved by calling the block. This block is called not once, but every time a new record is instantiated and default values need to be filled in. The latter form is especially useful if your model has a UUID column. One can generate a new, random UUID for every newly instantiated record: ```ruby class User < ActiveRecord::Base default_value_for :uuid do UuidGenerator.new.generate_uuid end end User.new.uuid # => "51d6d6846f1d1b5c9a...." User.new.uuid # => "ede292289e3484cb88...." ``` Note that record is passed to the block as an argument, in case you need it for whatever reason: ```ruby class User < ActiveRecord::Base default_value_for :uuid do |x| x # <--- a User object UuidGenerator.new.generate_uuid end end ``` ## default_value_for options * allows_nil (default: true) - Sets explicitly passed nil values if option is set to true. You can pass this options hash as 2nd parameter and have to pass the default value through the :value option in this case e.g.: ```ruby default_value_for :age, :value => 20, :allows_nil => false ``` You can still pass the default value through a block: ```ruby default_value_for :uuid, :allows_nil => false do UuidGenerator.new.generate_uuid end ```` ## The default_values method As a shortcut, you can use +default_values+ to set multiple default values at once. ```ruby default_values :age => 20, :uuid => lambda { UuidGenerator.new.generate_uuid } ``` If you like to override default_value_for options for each attribute you can do so: ```ruby default_values :age => { :value => 20 }, :uuid => { :value => lambda { UuidGenerator.new.generate_uuid }, :allows_nil => false } ``` The difference is purely aesthetic. If you have lots of default values which are constants or constructed with one-line blocks, +default_values+ may look nicer. If you have default values constructed by longer blocks, `default_value_for` suit you better. Feel free to mix and match. As a side note, due to specifics of Ruby's parser, you cannot say, ```ruby default_value_for :uuid { UuidGenerator.new.generate_uuid } ``` because it will not parse. One needs to write ```ruby default_value_for(:uuid) { UuidGenerator.new.generate_uuid } ``` instead. This is in part the inspiration for the +default_values+ syntax. ## Rules ### Instantiation of new record Upon instantiating a new record, the declared default values are filled into the record. You've already seen this in the above examples. ### Retrieval of existing record Upon retrieving an existing record in the following case, the declared default values are _not_ filled into the record. Consider the example with the UUID: ```ruby user = User.create user.uuid # => "529c91b8bbd3e..." user = User.find(user.id) # UUID remains unchanged because it's retrieved from the database! user.uuid # => "529c91b8bbd3e..." ``` But when the declared default value is set to not allow nil and nil is passed the default values will be set on retrieval. Consider this example: ```ruby default_value_for(:number, :allows_nil => false) { 123 } user = User.create # manual SQL by-passing active record and the default value for gem logic through ActiveRecord's after_initialize callback user.update_attribute(:number, nil) # declared default value should be set User.find(user.id).number # => 123 # = declared default value ``` ### Mass-assignment If a certain attribute is being assigned via the model constructor's mass-assignment argument, that the default value for that attribute will _not_ be filled in: ```ruby user = User.new(:uuid => "hello") user.uuid # => "hello" ``` However, if that attribute is protected by +attr_protected+ or +attr_accessible+, then it will be filled in: ```ruby class User < ActiveRecord::Base default_value_for :name, 'Joe' attr_protected :name end user = User.new(:name => "Jane") user.name # => "Joe" # the without protection option will work as expected user = User.new({:name => "Jane"}, :without_protection => true) user.name # => "Jane" ``` Explicitly set nil values for accessible attributes will be accepted: ```ruby class User < ActiveRecord::Base default_value_for :name, 'Joe' end user = User(:name => nil) user.name # => nil ... unless the accessible attribute is set to not allowing nil: class User < ActiveRecord::Base default_value_for :name, 'Joe', :allows_nil => false end user = User(:name => nil) user.name # => "Joe" ``` ### Inheritance Inheritance works as expected. All default values are inherited by the child class: ```ruby class User < ActiveRecord::Base default_value_for :name, 'Joe' end class SuperUser < User end SuperUser.new.name # => "Joe" ``` ### Attributes that aren't database columns `default_value_for` also works with attributes that aren't database columns. It works with anything for which there's an assignment method: ```ruby # Suppose that your 'users' table only has a 'name' column. class User < ActiveRecord::Base default_value_for :name, 'Joe' default_value_for :age, 20 default_value_for :registering, true attr_accessor :age def registering=(value) @registering = true end end user = User.new user.age # => 20 user.instance_variable_get('@registering') # => true ``` ### Default values are duplicated The given default values are duplicated when they are filled in, so if you mutate a value that was filled in with a default value, then it will not affect all subsequent default values: ```ruby class Author < ActiveRecord::Base # This model only has a 'name' attribute. end class Book < ActiveRecord::Base belongs_to :author # By default, a Book belongs to a new, unsaved author. default_value_for :author, Author.new end book1 = Book.new book1.author.name # => nil # This does not mutate the default value: book1.author.name = "John" book2 = Book.new book2.author.name # => nil ``` However the duplication is shallow. If you modify any objects that are referenced by the default value then it will affect subsequent default values: ```ruby class Author < ActiveRecord::Base attr_accessor :useless_hash default_value_for :useless_hash, { :foo => [] } end author1 = Author.new author1.useless_hash # => { :foo => [] } # This mutates the referred array: author1.useless_hash[:foo] << 1 author2 = Author.new author2.useless_hash # => { :foo => [1] } ``` You can prevent this from happening by passing a block to `default_value_for`, which returns a new object instance with fresh references every time: ```ruby class Author < ActiveRecord::Base attr_accessor :useless_hash default_value_for :useless_hash do { :foo => [] } end end author1 = Author.new author1.useless_hash # => { :foo => [] } author1.useless_hash[:foo] << 1 author2 = Author.new author2.useless_hash # => { :foo => [] } ``` ### Caveats A conflict can occur if your model class overrides the 'initialize' method, because this plugin overrides 'initialize' as well to do its job. ```ruby class User < ActiveRecord::Base def initialize # <-- this constructor causes problems super(:name => 'Name cannot be changed in constructor') end end ``` We recommend you to alias chain your initialize method in models where you use `default_value_for`: ```ruby class User < ActiveRecord::Base default_value_for :age, 20 def initialize_with_my_app initialize_without_my_app(:name => 'Name cannot be changed in constructor') end alias_method_chain :initialize, :my_app end ``` Also, stick with the following rules: * There is no need to +alias_method_chain+ your initialize method in models that don't use `default_value_for`. * Make sure that +alias_method_chain+ is called *after* the last `default_value_for` occurrence. If your default value is accidentally similar to default_value_for's options hash wrap your default value like this: ```ruby default_value_for :attribute_name, :value => { :value => 123, :other_value => 1234 } ``` ## When (not) to use default_value_for? You can also specify default values in the database schema. For example, you can specify a default value in a migration as follows: ```ruby create_table :users do |t| t.string :username, :null => false, :default => 'default username' t.integer :age, :null => false, :default => 20 end ``` This has similar effects as passing the default value as the second argument to `default_value_for`: ```ruby default_value_for(:username, 'default_username') default_value_for(:age, 20) ``` Default values are filled in whether you use the schema defaults or the default_value_for defaults: ```ruby user = User.new user.username # => 'default username' user.age # => 20 ``` It's recommended that you use this over `default_value_for` whenever possible. However, it's not possible to specify a schema default for serialized columns. With `default_value_for`, you can: ```ruby class User < ActiveRecord::Base serialize :color default_value_for :color, [255, 0, 0] end ``` And if schema defaults don't provide the flexibility that you need, then `default_value_for` is the perfect choice. For example, with `default_value_for` you could specify a per-environment default: ```ruby class User < ActiveRecord::Base if Rails.env ## "development" default_value_for :is_admin, true end end ``` Or, as you've seen in an earlier example, you can use `default_value_for` to generate a default random UUID: ```ruby class User < ActiveRecord::Base default_value_for :uuid do UuidGenerator.new.generate_uuid end end ``` Or you could use it to generate a timestamp that's relative to the time at which the record is instantiated: ```ruby class User < ActiveRecord::Base default_value_for :account_expires_at do 3.years.from_now end end User.new.account_expires_at # => Mon Sep 22 18:43:42 +0200 2008 sleep(2) User.new.account_expires_at # => Mon Sep 22 18:43:44 +0200 2008 ``` Finally, it's also possible to specify a default via an association: ```ruby # Has columns: 'name' and 'default_price' class SuperMarket < ActiveRecord::Base has_many :products end # Has columns: 'name' and 'price' class Product < ActiveRecord::Base belongs_to :super_market default_value_for :price do |product| product.super_market.default_price end end super_market = SuperMarket.create(:name => 'Albert Zwijn', :default_price => 100) soap = super_market.products.create(:name => 'Soap') soap.price # => 100 ``` ### What about before_validate/before_save? True, +before_validate+ and +before_save+ does what we want if we're only interested in filling in a default before saving. However, if one wants to be able to access the default value even before saving, then be prepared to write a lot of code. Suppose that we want to be able to access a new record's UUID, even before it's saved. We could end up with the following code: ```ruby # In the controller def create @user = User.new(params[:user]) @user.generate_uuid email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.") @user.save! end # Model class User < ActiveRecord::Base before_save :generate_uuid_if_necessary def generate_uuid self.uuid = ... end private def generate_uuid_if_necessary if uuid.blank? generate_uuid end end end ``` The need to manually call +generate_uuid+ here is ugly, and one can easily forget to do that. Can we do better? Let's see: ```ruby # Controller def create @user = User.new(params[:user]) email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.") @user.save! end # Model class User < ActiveRecord::Base before_save :generate_uuid_if_necessary def uuid value = read_attribute('uuid') if !value value = generate_uuid write_attribute('uuid', value) end value end # We need to override this too, otherwise User.new.attributes won't return # a default UUID value. I've never tested with User.create() so maybe we # need to override even more things. def attributes uuid super end private def generate_uuid_if_necessary uuid # Reader method automatically generates UUID if it doesn't exist end end ``` That's an awful lot of code. Using `default_value_for` is easier, don't you think? ### What about other plugins? I've only been able to find 2 similar plugins: * Default Value: http://agilewebdevelopment.com/plugins/default_value * ActiveRecord Defaults: http://agilewebdevelopment.com/plugins/activerecord_defaults 'Default Value' appears to be unmaintained; its SVN link is broken. This leaves only 'ActiveRecord Defaults'. However, it is semantically dubious, which leaves it wide open for corner cases. For example, it is not clearly specified what ActiveRecord Defaults will do when attributes are protected by +attr_protected+ or +attr_accessible+. It is also not clearly specified what one is supposed to do if one needs a custom +initialize+ method in the model. I've taken my time to thoroughly document default_value_for's behavior. ## Credits I've wanted such functionality for a while now and it baffled me that ActiveRecord doesn't provide a clean way for me to specify default values. After reading http://groups.google.com/group/rubyonrails-core/browse_thread/thread/b509a2fe2b62ac5/3e8243fa1954a935, it became clear that someone needs to write a plugin. This is the result. Thanks to Pratik Naik for providing the initial code snippet on which this plugin is based on: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model Thanks to Matthew Draper for Rails 5 support. Thanks to Norman Clarke and Tom Mango for Rails 4 support. default_value_for-3.4.0/init.rb0000644000004100000410000000214714022061274016526 0ustar www-datawww-data# Copyright (c) 2008, 2009, 2010 Phusion # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'default_value_for' default_value_for-3.4.0/LICENSE.TXT0000644000004100000410000000204714022061274016720 0ustar www-datawww-dataCopyright (c) 2008, 2009, 2010 Phusion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. default_value_for-3.4.0/Rakefile0000644000004100000410000000156514022061274016706 0ustar www-datawww-datatask :default => :test desc "Run unit tests." task :test do ruby "test.rb" end rails_versions = %w( 3.2 4.0 4.1 4.2 5.0 5.1 5.2 6.0 6.1 ) rails_versions.each do |version| dotless = version.delete('.') namespace :bundle do desc "Bundle with Rails #{version}.x" task :"rails#{dotless}" do ENV['BUNDLE_GEMFILE'] = "gemfiles/rails_#{dotless}.gemfile" sh "bundle" end end namespace :test do desc "Test with Rails #{version}.x" task :"rails#{dotless}" do ENV['BUNDLE_GEMFILE'] = "gemfiles/rails_#{dotless}.gemfile" ruby "test.rb" end end end namespace :test do desc "Test with all supported Rails versions" task :railsall do rails_versions.each do |version| dotless = version.delete('.') ENV['BUNDLE_GEMFILE'] = "gemfiles/rails_#{dotless}.gemfile" ruby "test.rb" end end end default_value_for-3.4.0/test.rb0000644000004100000410000003426314022061274016546 0ustar www-datawww-data# Copyright (c) 2008-2012 Phusion # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'bundler/setup' require 'minitest/autorun' require 'minitest/around/unit' require 'active_record' require 'action_pack' if ActiveSupport::VERSION::MAJOR == 3 require 'active_support/core_ext/logger' end if ActionPack::VERSION::MAJOR > 3 require 'action_controller' end # Handle an edge-case when using Arel 5 (i.e. Rails <= 4.1) with Ruby >= 2.4: # See: https://github.com/rails/arel/commit/dc85a6e9c74942945ad696f5da4d82490a85b865 # See: https://stackoverflow.com/a/51481088 rails_match_data = RUBY_VERSION.match(/\A(?\d+).(?\d+)/) rails_2_4_or_newer = rails_match_data[:major].to_i > 2 || (rails_match_data[:major].to_i == 2 && rails_match_data[:minor].to_i >= 4) arel_match_data = Arel::VERSION.match(/\A(?\d+).(?\d+)/) arel_older_than_7_1 = arel_match_data[:major].to_i < 7 || (arel_match_data[:major].to_i == 7 && arel_match_data[:minor].to_i < 1) if rails_2_4_or_newer && arel_older_than_7_1 module Arel module Visitors class DepthFirst < Arel::Visitors::Visitor alias :visit_Integer :terminal end class Dot < Arel::Visitors::Visitor alias :visit_Integer :visit_String end # The super class for ToSql changed with Arel 6 # See: https://github.com/rails/arel/commit/a6a7c75ff486657909e20e2f48764136caa5e87e#diff-3538aead5b80677372eea0e903ff728eR7 class ToSql < (Arel::VERSION[/\A\d+/].to_i >= 6 ? Arel::Visitors::Reduce : Arel::Visitors::Visitor) alias :visit_Integer :literal end end end end begin TestCaseClass = MiniTest::Test rescue NameError TestCaseClass = MiniTest::Unit::TestCase end require 'default_value_for' puts "\nTesting with Active Record version #{ActiveRecord::VERSION::STRING}" puts "\nTesting with Action Pack version #{ActionPack::VERSION::STRING}\n\n" ActiveRecord::Base.default_timezone = :local ActiveRecord::Base.logger = Logger.new(STDERR) ActiveRecord::Base.logger.level = Logger::WARN ActiveRecord::Base.establish_connection( :adapter => RUBY_PLATFORM == 'java' ? 'jdbcsqlite3' : 'sqlite3', :database => ':memory:' ) ActiveRecord::Base.connection.create_table(:users, :force => true) do |t| t.string :username t.integer :default_number t.text :settings end ActiveRecord::Base.connection.create_table(:books, :force => true) do |t| t.string :type t.integer :number t.integer :count, :null => false, :default => 1 t.integer :user_id t.timestamp :timestamp t.text :stuff t.boolean :flag end if defined?(Rails::Railtie) DefaultValueFor.initialize_railtie DefaultValueFor.initialize_active_record_extensions end class DefaultValuePluginTest < TestCaseClass def around Object.const_set(:User, Class.new(ActiveRecord::Base)) Object.const_set(:Book, Class.new(ActiveRecord::Base)) Object.const_set(:Novel, Class.new(Book)) User.has_many :books Book.belongs_to :user ActiveRecord::Base.transaction do yield raise ActiveRecord::Rollback end ensure Object.send(:remove_const, :User) Object.send(:remove_const, :Book) Object.send(:remove_const, :Novel) ActiveSupport::Dependencies.clear end def test_default_value_on_attribute_methods Book.class_eval do serialize :stuff default_value_for :color, :green def color; (self.stuff || {})[:color]; end def color=(val) self.stuff ||= {} self.stuff[:color] = val end end assert_equal :green, Book.create.color end def test_default_value_can_be_passed_as_argument Book.default_value_for(:number, 1234) assert_equal 1234, Book.new.number end def test_default_value_can_be_passed_as_block Book.default_value_for(:number) { 1234 } assert_equal 1234, Book.new.number end def test_works_with_create Book.default_value_for :number, 1234 object = Book.create refute_nil Book.find_by_number(1234) # allows nil for existing records object.update_attribute(:number, nil) assert_nil Book.find_by_number(1234) assert_nil Book.find(object.id).number end def test_does_not_allow_nil_sets_default_value_on_existing_nils Book.default_value_for(:number, :allows_nil => false) { 1234 } object = Book.create object.update_attribute(:number, nil) assert_nil Book.find_by_number(1234) assert_equal 1234, Book.find(object.id).number end def test_overwrites_db_default Book.default_value_for :count, 1234 assert_equal 1234, Book.new.count end def test_doesnt_overwrite_values_provided_by_mass_assignment Book.default_value_for :number, 1234 assert_equal 1, Book.new(:number => 1, :count => 2).number end def test_doesnt_overwrite_values_provided_by_multiparameter_assignment Book.default_value_for :timestamp, Time.mktime(2000, 1, 1) timestamp = Time.mktime(2009, 1, 1) object = Book.new('timestamp(1i)' => '2009', 'timestamp(2i)' => '1', 'timestamp(3i)' => '1') assert_equal timestamp, object.timestamp end def test_doesnt_overwrite_values_provided_by_constructor_block Book.default_value_for :number, 1234 object = Book.new do |x| x.number = 1 x.count = 2 end assert_equal 1, object.number end def test_doesnt_overwrite_explicitly_provided_nil_values_in_mass_assignment Book.default_value_for :number, 1234 assert_nil Book.new(:number => nil).number end def test_overwrites_explicitly_provided_nil_values_in_mass_assignment Book.default_value_for :number, :value => 1234, :allows_nil => false assert_equal 1234, Book.new(:number => nil).number end def test_default_values_are_inherited Book.default_value_for :number, 1234 assert_equal 1234, Novel.new.number end def test_default_values_in_superclass_are_saved_in_subclass Book.default_value_for :number, 1234 Novel.default_value_for :flag, true object = Novel.create! assert_equal object.id, Novel.find_by_number(1234).id assert_equal object.id, Novel.find_by_flag(true).id end def test_default_values_in_subclass Novel.default_value_for :number, 5678 assert_equal 5678, Novel.new.number assert_nil Book.new.number end def test_multiple_default_values_in_subclass_with_default_values_in_parent_class Book.class_eval do default_value_for :other_number, nil attr_accessor :other_number end Novel.default_value_for :number, 5678 # Ensure second call in this class doesn't reset _default_attribute_values, # and also doesn't consider the parent class' _default_attribute_values when # making that check. Novel.default_value_for :user_id, 9999 object = Novel.new assert_nil object.other_number assert_equal 5678, object.number assert_equal 9999, object.user_id end def test_override_default_values_in_subclass Book.default_value_for :number, 1234 Novel.default_value_for :number, 5678 assert_equal 5678, Novel.new.number assert_equal 1234, Book.new.number end def test_default_values_in_subclass_do_not_affect_parent_class Book.default_value_for :number, 1234 Novel.class_eval do default_value_for :hello, "hi" attr_accessor :hello end assert Book.new assert !Book._default_attribute_values.include?(:hello) end def test_doesnt_set_default_on_saved_records Book.create(:number => 9876) Book.default_value_for :number, 1234 assert_equal 9876, Book.first.number end def test_also_works_on_attributes_that_arent_database_columns Book.class_eval do default_value_for :hello, "hi" attr_accessor :hello end assert_equal 'hi', Book.new.hello end def test_works_on_attributes_that_only_have_writers Book.class_eval do default_value_for :hello, "hi" attr_writer :hello end assert_equal 'hi', Book.new.instance_variable_get('@hello') end def test_doesnt_conflict_with_overrided_initialize_method_in_model_class Book.class_eval do def initialize(attrs = {}) @initialized = true super(:count => 5678) end default_value_for :number, 1234 end object = Book.new assert_equal 1234, object.number assert_equal 5678, object.count assert object.instance_variable_get('@initialized') end def test_model_instance_is_passed_to_the_given_block instance = nil Book.default_value_for :number do |n| instance = n end object = Book.new assert_same object.object_id, instance.object_id end def test_can_specify_default_value_via_association user = User.create(:username => 'Kanako', :default_number => 123) Book.default_value_for :number do |n| n.user.default_number end assert_equal 123, user.books.create!.number end def test_default_values Book.default_values({ :type => "normal", :number => lambda { 10 + 5 }, :timestamp => lambda {|_| Time.now } }) object = Book.new assert_equal("normal", object.type) assert_equal(15, object.number) end def test_default_value_order Book.default_value_for :count, 5 Book.default_value_for :number do |this| this.count * 2 end object = Book.new assert_equal(5, object.count) assert_equal(10, object.number) end def test_attributes_with_default_values_are_not_marked_as_changed Book.default_value_for :count, 5 Book.default_value_for :number, 2 object = Book.new assert(!object.changed?) assert_equal([], object.changed) object.type = "foo" assert(object.changed?) assert_equal(["type"], object.changed) end def test_default_values_are_duplicated User.default_value_for :username, "hello" user1 = User.new user1.username << " world" user2 = User.new assert_equal("hello", user2.username) end def test_default_values_are_shallow_copied User.class_eval do attr_accessor :hash default_value_for :hash, { 1 => [] } end user1 = User.new user1.hash[1] << 1 user2 = User.new assert_equal([1], user2.hash[1]) end def test_constructor_does_not_affect_the_hash_passed_to_it Book.default_value_for :count, 5 options = { :count => 5, :user_id => 1 } options_dup = options.dup Book.new(options) assert_equal(options_dup, options) end def test_subclass_find Book.default_value_for :number, 5678 n = Novel.create assert Novel.find(n.id) end def test_does_not_see_false_as_blank_at_boolean_columns_for_existing_records Book.default_value_for(:flag, :allows_nil => false) { true } object = Book.create # allows nil for existing records object.update_attribute(:flag, false) assert_equal false, Book.find(object.id).flag end def test_works_with_nested_attributes User.accepts_nested_attributes_for :books User.default_value_for :books do [Book.create!(:number => 0)] end user = User.create! :books_attributes => [{:number => 1}] assert_equal 1, Book.all.first.number end def test_works_with_stored_attribute_accessors_when_initializing_value_that_does_not_allow_nil User.store :settings, :accessors => :bio User.default_value_for :bio, :value => 'None given', :allows_nil => false user = User.create!(:bio => 'This is a bio') assert_equal 'This is a bio', user.bio end if ActionPack::VERSION::MAJOR > 3 def test_doesnt_overwrite_explicitly_provided_nil_values_in_mass_assignment_with_action_controller_parameters Book.default_value_for :number, 1234 assert_nil Book.new(ActionController::Parameters.new(:number => nil).permit!).number end def test_overwrites_explicitly_provided_nil_values_in_mass_assignment_with_action_controller_parameters Book.default_value_for :number, :value => 1234, :allows_nil => false assert_equal 1234, Book.new(ActionController::Parameters.new(:number => nil).permit!).number end def test_works_with_nested_attributes_with_action_controller_parameters User.accepts_nested_attributes_for :books User.default_value_for :books do [Book.create!(:number => 0)] end user = User.create!(ActionController::Parameters.new(:books_attributes => [{:number => 1}]).permit!) assert_equal 1, Book.all.first.number end def test_works_with_stored_attribute_accessors_when_initializing_value_that_does_not_allow_nil_with_action_controller_parameters User.store :settings, :accessors => :bio User.default_value_for :bio, :value => 'None given', :allows_nil => false user = User.create!(ActionController::Parameters.new(:bio => 'This is a bio').permit!) assert_equal 'This is a bio', user.bio end end if ActiveRecord::VERSION::MAJOR == 3 def test_constructor_ignores_forbidden_mass_assignment_attributes Book.class_eval do default_value_for :number, 1234 attr_protected :number end object = Book.new(:number => 5678, :count => 987) assert_equal 1234, object.number assert_equal 987, object.count end def test_constructor_respects_without_protection_option Book.class_eval do default_value_for :number, 1234 attr_protected :number end object = Book.create!({:number => 5678, :count => 987}, :without_protection => true) assert_equal 5678, object.number assert_equal 987, object.count end end end default_value_for-3.4.0/lib/0000755000004100000410000000000014022061274016000 5ustar www-datawww-datadefault_value_for-3.4.0/lib/default_value_for/0000755000004100000410000000000014022061274021466 5ustar www-datawww-datadefault_value_for-3.4.0/lib/default_value_for/railtie.rb0000644000004100000410000000301314022061274023441 0ustar www-datawww-data# Copyright (c) 2008, 2009, 2010, 2011 Phusion # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module DefaultValueFor def self.initialize_railtie ActiveSupport.on_load :active_record do DefaultValueFor.initialize_active_record_extensions end end def self.initialize_active_record_extensions ActiveRecord::Base.extend(DefaultValueFor::ClassMethods) end class Railtie < Rails::Railtie initializer 'default_value_for.insert_into_active_record' do DefaultValueFor.initialize_railtie end end end default_value_for-3.4.0/lib/default_value_for.rb0000644000004100000410000001633014022061274022016 0ustar www-datawww-data# Copyright (c) 2008-2012 Phusion # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module DefaultValueFor class NormalValueContainer def initialize(value) @value = value end def evaluate(instance) if @value.duplicable? return @value.dup else return @value end end end class BlockValueContainer def initialize(block) @block = block end def evaluate(instance) if @block.arity == 0 return @block.call else return @block.call(instance) end end end module ClassMethods # Declares a default value for the given attribute. # # Sets the default value to the given options parameter unless the given options equal { :value => ... } # # The options can be used to specify the following things: # * value - Sets the default value. # * allows_nil (default: true) - Sets explicitly passed nil values if option is set to true. def default_value_for(attribute, options = {}, &block) value = options allows_nil = true if options.is_a?(Hash) opts = options.stringify_keys value = opts.fetch('value', options) allows_nil = opts.fetch('allows_nil', true) end if !method_defined?(:set_default_values) include(InstanceMethods) after_initialize :set_default_values class_attribute :_default_attribute_values class_attribute :_default_attribute_values_not_allowing_nil extend(DelayedClassMethods) init_hash = true else init_hash = !singleton_methods(false).include?(:_default_attribute_values) end if init_hash self._default_attribute_values = {} self._default_attribute_values_not_allowing_nil = [] end if block_given? container = BlockValueContainer.new(block) else container = NormalValueContainer.new(value) end _default_attribute_values[attribute.to_s] = container _default_attribute_values_not_allowing_nil << attribute.to_s unless allows_nil end def default_values(values) values.each_pair do |key, options| options = options.stringify_keys if options.is_a?(Hash) value = options.is_a?(Hash) && options.has_key?('value') ? options['value'] : options if value.kind_of? Proc default_value_for(key, options.is_a?(Hash) ? options : {}, &value) else default_value_for(key, options) end end end end module DelayedClassMethods def _all_default_attribute_values return _default_attribute_values unless superclass.respond_to?(:_default_attribute_values) superclass._all_default_attribute_values.merge(_default_attribute_values) end def _all_default_attribute_values_not_allowing_nil return _default_attribute_values_not_allowing_nil unless superclass.respond_to?(:_default_attribute_values_not_allowing_nil) result = superclass._all_default_attribute_values_not_allowing_nil + _default_attribute_values_not_allowing_nil result.uniq! result end end module InstanceMethods def initialize(attributes = nil, options = {}) attributes = attributes.to_h if attributes.respond_to?(:to_h) @initialization_attributes = attributes.is_a?(Hash) ? attributes.stringify_keys : {} unless options[:without_protection] if respond_to?(:mass_assignment_options, true) && options.has_key?(:as) @initialization_attributes = sanitize_for_mass_assignment(@initialization_attributes, options[:as]) elsif respond_to?(:sanitize_for_mass_assignment, true) @initialization_attributes = sanitize_for_mass_assignment(@initialization_attributes) else @initialization_attributes = remove_attributes_protected_from_mass_assignment(@initialization_attributes) end end if self.class.respond_to? :protected_attributes super(attributes, options) else super(attributes) end end def attributes_for_create(attribute_names) attribute_names += self.class._all_default_attribute_values.keys.map(&:to_s).find_all { |name| self.class.columns_hash.key?(name) } super end def set_default_values self.class._all_default_attribute_values.each do |attribute, container| next unless new_record? || self.class._all_default_attribute_values_not_allowing_nil.include?(attribute) connection_default_value_defined = new_record? && respond_to?("#{attribute}_changed?") && !__send__("#{attribute}_changed?") attribute_blank = if attributes.has_key?(attribute) column = self.class.columns_hash[attribute] if column && column.type == :boolean attributes[attribute].nil? else attributes[attribute].blank? end elsif respond_to?(attribute) send(attribute).nil? else instance_variable_get("@#{attribute}").nil? end next unless connection_default_value_defined || attribute_blank # allow explicitly setting nil through allow nil option next if @initialization_attributes.is_a?(Hash) && ( @initialization_attributes.has_key?(attribute) || ( @initialization_attributes.has_key?("#{attribute}_attributes") && nested_attributes_options.stringify_keys[attribute] ) ) && !self.class._all_default_attribute_values_not_allowing_nil.include?(attribute) __send__("#{attribute}=", container.evaluate(self)) if respond_to?(:clear_attribute_changes, true) clear_attribute_changes [attribute] if has_attribute?(attribute) else changed_attributes.delete(attribute) end end end end end if defined?(Rails::Railtie) require 'default_value_for/railtie' else # For anybody is using AS and AR without Railties, i.e. Padrino. ActiveRecord::Base.extend(DefaultValueFor::ClassMethods) end default_value_for-3.4.0/default_value_for.gemspec0000644000004100000410000000217614022061274022273 0ustar www-datawww-dataGem::Specification.new do |s| s.name = %q{default_value_for} s.version = "3.4.0" s.summary = %q{Provides a way to specify default values for ActiveRecord models} s.description = %q{The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner} s.email = %q{software-signing@phusion.nl} s.homepage = %q{https://github.com/FooBarWidget/default_value_for} s.authors = ["Hongli Lai"] s.license = 'MIT' s.required_ruby_version = '>= 1.9.3' s.files = ['default_value_for.gemspec', 'LICENSE.TXT', 'Rakefile', 'README.md', 'test.rb', 'init.rb', 'lib/default_value_for.rb', 'lib/default_value_for/railtie.rb' ] s.add_dependency 'activerecord', '>= 3.2.0', '< 7.0' s.add_development_dependency 'actionpack', '>= 3.2.0', '< 7.0' s.add_development_dependency 'railties', '>= 3.2.0', '< 7.0' s.add_development_dependency 'minitest', '>= 4.2' s.add_development_dependency 'minitest-around' s.add_development_dependency 'appraisal' end