kriss-associated_records_on_steroids 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ == 0.1.0
2
+ * Auto builder for associated records
3
+ * Add merge option to ActiveRecord errors
4
+ * Overwriten validates_associated method in ActiveRecord
5
+ * Few tests of basic functionality
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kriss Kowalik
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.rdoc ADDED
@@ -0,0 +1,65 @@
1
+ Plugin allowing to auto build and validate associated records and merge validation errors
2
+
3
+ == Examples
4
+
5
+ If you want automatically build specified associated recrods when new record is
6
+ initialized you can use auto_build:
7
+
8
+ class Bar < ActiveRecord::Base
9
+ belongs_to :foo
10
+ end
11
+
12
+ class Foo < ActiveRecord::Base
13
+ has_one :bar
14
+ set_associated_records_to_auto_build :bar
15
+ end
16
+
17
+ foo = Foo.new
18
+ foo.bar # => #<Bar:0x...>
19
+
20
+ You can also validate associated record in easy way:
21
+
22
+ class Person < ActiveRecord::Base
23
+ has_one :address
24
+ validates_presence_of :name
25
+ validates_associated_record :address
26
+ delegate :street, :street=, :city, :city=, :state, :state=,
27
+ :zip, :zip=, :to => :address
28
+ end
29
+
30
+ class Address < ActiveRecord::Base
31
+ validates_presence_of :street, :city, :state, :zip
32
+ end
33
+
34
+ joe = Person.new
35
+ joe.valid? # => false
36
+
37
+ joe.errors.on(:name) # => "can't be blank"
38
+ joe.errors.on(:street) # => "can't be blank"
39
+ joe.errors.on(:city) # => "can't be blank"
40
+ joe.errors.on(:state) # => "can't be blank"
41
+ joe.errors.on(:zip) # => "can't be blank"
42
+
43
+ And of course merging errors in not associated records:
44
+
45
+ class Person < ActiveRecord::Base
46
+ validates_presence_of :name
47
+ end
48
+
49
+ class Address < ActiveRecord::Base
50
+ validates_presence_of :street, :city, :state, :zip
51
+ end
52
+
53
+ joe = Person.new
54
+ joe.valid? # => false
55
+ address = Address.new
56
+ address.valid? # => false
57
+
58
+ joe.errors.merge address.errors
59
+ joe.errors.on(:name) # => "can't be blank"
60
+ joe.errors.on(:street) # => "can't be blank"
61
+ joe.errors.on(:city) # => "can't be blank"
62
+ joe.errors.on(:state) # => "can't be blank"
63
+ joe.errors.on(:zip) # => "can't be blank"
64
+
65
+ Enjoy!
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'Dffddf'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
data/TODO.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ == 0.2
2
+ * All tests
3
+ * More exhaustive readme file
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ minor: 1
3
+ patch: 0
4
+ major: 0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "associated_records_on_steroids"
@@ -0,0 +1,91 @@
1
+ module ActiveRecord
2
+ class Base
3
+ class << self
4
+ # Set list of associated recodrs, which you want to build after
5
+ # initialize current model
6
+ #
7
+ # == Usage
8
+ #
9
+ # class Bar < ActiveRecord::Base
10
+ # belongs_to :foo
11
+ # end
12
+ #
13
+ # class Foo < ActiveRecord::Base
14
+ # has_one :bar
15
+ # set_associated_records_to_auto_build :bar
16
+ # end
17
+ #
18
+ # foo = Foo.new
19
+ # foo.bar # => #<Bar:0x...>
20
+ def set_associated_records_to_auto_build(*names)
21
+ if names.is_a? Array
22
+ self.associated_records_to_auto_build = names
23
+ else
24
+ raise ArgumentError.new("List of associated records to auto build have to be given in array")
25
+ end
26
+ end
27
+
28
+ # Add associated records which you want to automatically build at the end
29
+ # of autobuild queue
30
+ def append_associated_records_to_auto_build(*names)
31
+ self.associated_records_to_auto_build.concat(names)
32
+ end
33
+
34
+ # Add associated records which you want to automatically build at the begin
35
+ # of autobuild queue
36
+ def prepend_associated_records_to_auto_build(*names)
37
+ self.associated_records_to_auto_build.unshift(*names)
38
+ end
39
+ end
40
+
41
+ # Queue of records to auto build
42
+ @@associated_records_to_auto_build = {}
43
+ cattr_accessor :associated_records_to_auto_build
44
+
45
+ # All records from autobuild queue should be created after initialization
46
+ def after_initialize
47
+ build_associated_records
48
+ end
49
+
50
+ # List of automatically created objects for current model
51
+ def self.associated_records_to_auto_build
52
+ @@associated_records_to_auto_build[self.to_s] ||= []
53
+ end
54
+
55
+ # TODO: should i remove it?
56
+ def self.associated_records_to_auto_build=(val)
57
+ @@associated_records_to_auto_build[self.to_s] = val
58
+ end
59
+
60
+ # Build all associated records from autobuild queue
61
+ #
62
+ # == Options
63
+ #
64
+ # * +only+ - build only specified associations
65
+ # * +except+ - build all associations except specified in this parameter
66
+ def build_associated_records(*opts)
67
+ defaults = { :except => [], :only => [] }
68
+ opts = defaults.merge(opts.extract_options!)
69
+ # Find all associations for recrods specified in queue
70
+ all_associations = {}
71
+ self.class.reflect_on_all_associations.each {|a| all_associations[a.name] = a.macro }
72
+ # Build records
73
+ self.class.associated_records_to_auto_build.each do |name|
74
+ name = name.to_sym
75
+ next if !opts[:except].empty? && opts[:except].include?(name)
76
+ next if !opts[:only].empty? && !opts[:only].include?(name)
77
+ if all_associations.include?(name)
78
+ if all_associations[name] == :has_many
79
+ if records = self.send(name)
80
+ records.build if records.empty?
81
+ end
82
+ elsif [:has_one, :belongs_to].include?(all_associations[name])
83
+ self.send("build_#{name.to_s}".to_sym) unless self.send(name.to_sym)
84
+ end
85
+ else
86
+ raise ArgumentError.new("There is no association for '#{name}' in #{self.class.to_s}")
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,55 @@
1
+ # This code snippet I found somewhere on snippets.dzone.com
2
+ # and modify little bit. Thanks to author whoever hi is :)
3
+
4
+ module ActiveRecord
5
+ class Errors
6
+ # Merges the errors from the passed in errors object onto this errors object.
7
+ #
8
+ # == Examples
9
+ #
10
+ # class Person < ActiveRecord::Base
11
+ # validates_presence_of :name
12
+ # end
13
+ #
14
+ # class Address < ActiveRecord::Base
15
+ # validates_presence_of :street, :city, :state, :zip
16
+ # end
17
+ #
18
+ # joe = Person.new
19
+ # joe.valid? # => false
20
+ #
21
+ # address = Address.new
22
+ # address.valid? # => false
23
+ #
24
+ # joe.errors.merge address.errors
25
+ # joe.errors.on(:name) # => "can't be blank"
26
+ # joe.errors.on(:street) # => "can't be blank"
27
+ # joe.errors.on(:city) # => "can't be blank"
28
+ # joe.errors.on(:state) # => "can't be blank"
29
+ # joe.errors.on(:zip) # => "can't be blank"
30
+ #
31
+ # == Options
32
+ #
33
+ # * +only+ - can take a single field or an array of fields, it merges errors from the specified fields
34
+ # * +except+ - can take a single field or an array of fields, it merges all errors except the on the fields specified
35
+ def merge(errors, options={})
36
+ fields_to_merge = if only=options[:only]
37
+ only
38
+ elsif except=options[:except]
39
+ except = [except] unless except.is_a?(Array)
40
+ except.map!(&:to_sym)
41
+ errors.entries.map(&:first).select do |field|
42
+ !except.include?(field.to_sym)
43
+ end
44
+ else
45
+ errors.entries.map(&:first)
46
+ end
47
+ fields_to_merge = [fields_to_merge] unless fields_to_merge.is_a?(Array)
48
+ fields_to_merge.map!(&:to_sym)
49
+
50
+ errors.entries.each do |field, msg|
51
+ add field, msg if fields_to_merge.include?(field.to_sym)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveRecord
2
+ module Validations
3
+ module ClassMethods
4
+ # This overwritten method will merge errors from associated record
5
+ # when it's invalid
6
+ #
7
+ # === Examples
8
+ #
9
+ # class Person < ActiveRecord::Base
10
+ # has_one :address
11
+ # validates_presence_of :name
12
+ # validates_associated_record :address
13
+ # delegate :street, :street=, :city, :city=, :state, :state=,
14
+ # :zip, :zip=, :to => :address
15
+ # end
16
+ #
17
+ # class Address < ActiveRecord::Base
18
+ # validates_presence_of :street, :city, :state, :zip
19
+ # end
20
+ #
21
+ # joe = Person.new
22
+ # joe.valid? # => false
23
+ #
24
+ # joe.errors.on(:name) # => "can't be blank"
25
+ # joe.errors.on(:street) # => "can't be blank"
26
+ # joe.errors.on(:city) # => "can't be blank"
27
+ # joe.errors.on(:state) # => "can't be blank"
28
+ # joe.errors.on(:zip) # => "can't be blank"
29
+ def validates_associated(*attr_names)
30
+ configuration = { :on => :save }
31
+ configuration.update(attr_names.extract_options!)
32
+ send(validation_method(configuration[:on]), configuration) do |record|
33
+ attr_names.each do |attr_name|
34
+ if associated_record = record.send(attr_name.to_sym)
35
+ unless associated_record.valid?
36
+ record.errors.merge(associated_record.errors)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ require "active_record/errors"
2
+ require "active_record/validates_associated_record"
3
+ require "active_record/build_associated_records"
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class AssociatedRecordsTest < Test::Unit::TestCase
4
+ context "Using merge errors feature" do
5
+ should "merge Addres and Person errors" do
6
+ maria = Person.new
7
+ marias_place = Address.new
8
+ assert !maria.save
9
+ assert !marias_place.save
10
+ maria.errors.merge(marias_place.errors)
11
+ assert_equal "can't be blank", maria.errors.on(:name)
12
+ assert_equal "can't be blank", maria.errors.on(:street)
13
+ end
14
+ end
15
+
16
+ context "Creating new record" do
17
+ should "merge Addres and Resident errors when are invalid" do
18
+ joe = Resident.new
19
+ # address is created automatically but we would like to check
20
+ # this way of validation too
21
+ joe.address = Address.new
22
+ assert !joe.save
23
+ assert_equal "can't be blank", joe.errors.on(:name)
24
+ assert_equal "can't be blank", joe.errors.on(:street)
25
+ end
26
+
27
+ should "build associated Address after Person initialize" do
28
+ joe = Resident.new
29
+ assert !joe.address.nil?
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ class Address < ActiveRecord::Base
2
+ def self.columns; []; end
3
+ attr_accessor :street, :city, :state, :zip, :person_id
4
+ validates_presence_of :street, :city, :state, :zip
5
+ belongs_to :person
6
+ end
@@ -0,0 +1,6 @@
1
+ class Person < ActiveRecord::Base
2
+ def self.columns; []; end
3
+ attr_accessor :name
4
+ has_one :address
5
+ validates_presence_of :name
6
+ end
@@ -0,0 +1,7 @@
1
+ class Resident < Person
2
+ def self.columns; []; end
3
+ validates_associated :address
4
+ delegate :street, :street=, :city, :city=, :state, :state=,
5
+ :zip, :zip=, :to => :address
6
+ set_associated_records_to_auto_build :address
7
+ end
@@ -0,0 +1,12 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ ENV['RAILS_ROOT'] ||= File.join(File.dirname(__FILE__), '../../../../')
3
+
4
+ require 'rubygems'
5
+ require 'shoulda'
6
+ require 'test/unit'
7
+
8
+ require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
9
+
10
+ require File.dirname(__FILE__) + '/models/address'
11
+ require File.dirname(__FILE__) + '/models/person'
12
+ require File.dirname(__FILE__) + '/models/resident'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kriss-associated_records_on_steroids
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kriss Kowalik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-27 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Plugin allowing to auto build and validate associated records and merge validation errors
17
+ email: kriss.kowalik@gmail.pl
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - MIT-LICENSE
25
+ - CHANGELOG.rdoc
26
+ files:
27
+ - CHANGELOG.rdoc
28
+ - MIT-LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - TODO.rdoc
32
+ - VERSION.yml
33
+ - init.rb
34
+ - lib/active_record/build_associated_records.rb
35
+ - lib/active_record/errors.rb
36
+ - lib/active_record/validates_associated_record.rb
37
+ - lib/associated_records_on_steroids.rb
38
+ - test/associated_records_on_steroids_test.rb
39
+ - test/models/address.rb
40
+ - test/models/person.rb
41
+ - test/models/resident.rb
42
+ - test/test_helper.rb
43
+ has_rdoc: false
44
+ homepage: http://krisskowalik.pl
45
+ licenses:
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --main
49
+ - README.rdoc
50
+ - --inline-source
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: Plugin allowing to auto build and validate associated records and merge validation errors
73
+ test_files:
74
+ - test/associated_records_on_steroids_test.rb
75
+ - test/models/address.rb
76
+ - test/models/person.rb
77
+ - test/models/resident.rb
78
+ - test/test_helper.rb