object_attorney 1.0.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NGI4MzlkYzA2YTkxOTc0Yzc1ZWJkOGIxYzk1Y2NjNGExY2Q2YWExOQ==
4
+ YzdjZjUwYTk2MGM5NDM2Nzg3MTE4YmY3ODVhZDVlODQ4MmRiMmRjYg==
5
5
  data.tar.gz: !binary |-
6
- ZjBkNGJmMTNmMjgxYWY0NjRhYjgxZmNlNWI1MDliY2JmMjMwMzQ2NA==
6
+ NjQ0YzRjYjg3YTlkMzgyYTU5Njk4MTNmMTkwNDU2NWRiNWMxNmE2NQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YjI0MzM3ZTRjMzM2MTYxYmIxZTg0ZTU4YzRkNjkwMzk0MTYzOTZjNDI1NGIz
10
- Y2IxNDJjY2U3N2U0NzhlOGJhMzViMTQxYmYzZmQ1ZDIwZDhhNzFiNzNjODk5
11
- MzQyNThmNjU5ZjdiMGRlODM0MGZjZDY4ODA2ZTdlY2JhMDRkNmI=
9
+ NTYwMTk5MGNjNzI4NTQwMWM4ZGFlOTBkY2JkMDYwZTdmZmQwMjk0ZGVjMDZl
10
+ NjIwZmUxNWY3ZThlNTViNTg5YjRiZjMwYTRiZDAyYTZmMGM5ZjJhYzg4ZGM5
11
+ YThhODIzOTFjMzAzMmE4YjM3MDgyMzhkNTlkOGRlYjlkODkwOWQ=
12
12
  data.tar.gz: !binary |-
13
- ZmU1NWM2NTJjMGY3MjlmOGJkNGQzZTUxMzVjY2RiMTVlZjQ2OTNjOGNlMjU2
14
- NTAyMzg5YTk0ZTBjOTIyYWY2OThhMTkxYjhhOWFmZDk5ZTMxMTkyMzY3MTQz
15
- MWU3NmZjM2I2Y2YzNGI2NWFhODIyNDFjNGI2YzE0Y2I2MTU1NTI=
13
+ MzQzNGRkZDIwMjYwOGVlMjFkMjkzYTA4MGM2OTQ5ZDk1OTdiNmQxZjE3MGM0
14
+ MTdmMGJmOWIzZGI2MzAwYWJmZTZhOGQ2YTM0MzczYmNlZWYzYzZlODUxYmU0
15
+ YjU4ODQ1MmYwZDkxMzk4MDBiNGViMWFhZGQyNWY2YWM1OTAyODQ=
data/.gitignore CHANGED
@@ -14,5 +14,6 @@ rdoc
14
14
  spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
- tmp
18
- *.DS_Store
17
+ tmp/*
18
+ *.DS_Store
19
+ db/test.sqlite3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --tty
2
+ --color
3
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3@object_attorney --create
data/Gemfile CHANGED
@@ -2,3 +2,15 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in object_attorney.gemspec
4
4
  gemspec
5
+
6
+ group :development, :test do
7
+ gem "rspec", "~> 2.11"
8
+ gem "sqlite3"
9
+ gem "activerecord"
10
+ #gem 'database_cleaner'
11
+ gem "pry"
12
+
13
+ unless ENV["CI"]
14
+ gem "guard-rspec", "~> 0.7"
15
+ end
16
+ end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, :cli => "--color --fail-fast -I. -fs" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/Rakefile CHANGED
@@ -1 +1,19 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ # see: http://blog.aizatto.com/2007/05/27/activerecord-migrations-without-rails/
4
+ # require 'active_record'
5
+ # require 'yaml'
6
+
7
+ # ENV["RAILS_ENV"] ||= 'test'
8
+
9
+ # task :default => :migrate
10
+
11
+ # desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
12
+ # task :migrate => :environment do
13
+ # ActiveRecord::Migrator.migrate('db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
14
+ # end
15
+
16
+ # task :environment do
17
+ # ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))[ENV["RAILS_ENV"]])
18
+ # ActiveRecord::Base.logger = Logger.new(File.open('tmp/database.log', 'a'))
19
+ # end
@@ -0,0 +1,14 @@
1
+ class CreatePosts < ActiveRecord::Migration
2
+ def up
3
+ create_table :posts do |t|
4
+ t.string :title
5
+ t.text :body
6
+ t.boolean :admin, default: false
7
+ t.timestamps
8
+ end
9
+ end
10
+
11
+ def down
12
+ drop_table :posts
13
+ end
14
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,22 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 20131020175047) do
14
+
15
+ create_table "posts", force: true do |t|
16
+ t.string "title"
17
+ t.text "body"
18
+ t.datetime "created_at"
19
+ t.datetime "updated_at"
20
+ end
21
+
22
+ end
@@ -2,7 +2,7 @@ module ObjectAttorney
2
2
  module NestedObjects
3
3
 
4
4
  def nested_objects
5
- @@nested_objects.map { |nested_object_sym| self.send(nested_object_sym) }.flatten
5
+ self.class.nested_objects.map { |nested_object_sym| self.send(nested_object_sym) }.flatten
6
6
  end
7
7
 
8
8
  def mark_for_destruction
@@ -18,11 +18,17 @@ module ObjectAttorney
18
18
 
19
19
  _destroy = attributes["_destroy"] || attributes[:_destroy]
20
20
 
21
- object.mark_for_destruction if ["true", "1"].include?(_destroy)
21
+ object.mark_for_destruction if ["true", "1", true].include?(_destroy)
22
22
  end
23
23
 
24
24
  protected #################### PROTECTED METHODS DOWN BELOW ######################
25
25
 
26
+ def save_nested_objects(save_method)
27
+ nested_objects.map do |nested_object|
28
+ call_save_or_destroy(nested_object, save_method)
29
+ end.all?
30
+ end
31
+
26
32
  def validate_nested_objects
27
33
  #nested_objects.all?(&:valid?) #will not validate all nested_objects
28
34
  return true if nested_objects.reject(&:marked_for_destruction?).map(&:valid?).all?
@@ -31,7 +37,7 @@ module ObjectAttorney
31
37
  end
32
38
 
33
39
  def import_nested_objects_errors
34
- @@nested_objects.map do |nested_object_sym|
40
+ self.class.nested_objects.map do |nested_object_sym|
35
41
 
36
42
  [*self.send(nested_object_sym)].each do |nested_object|
37
43
  nested_object.errors.full_messages.each { |message| self.errors.add(nested_object_sym, message) }
@@ -54,8 +60,6 @@ module ObjectAttorney
54
60
  base.class_eval do
55
61
  validate :validate_nested_objects
56
62
  end
57
-
58
- @@nested_objects = []
59
63
  end
60
64
 
61
65
  def attributes_without_destroy(attributes)
@@ -101,7 +105,7 @@ module ObjectAttorney
101
105
 
102
106
  def build_new_nested_objects(existing_and_new_nested_objects, nested_object_name)
103
107
  (send("#{nested_object_name}_attributes") || {}).values.each do |attributes|
104
- next if attributes["id"].present?
108
+ next if attributes["id"].present? || attributes[:id].present?
105
109
 
106
110
  new_nested_object = send("build_#{nested_object_name.to_s.singularize}", attributes_without_destroy(attributes))
107
111
  mark_for_destruction_if_necessary(new_nested_object, attributes)
@@ -122,6 +126,10 @@ module ObjectAttorney
122
126
  nil
123
127
  end
124
128
 
129
+ def nested_objects
130
+ self.instance_variable_get("@nested_objects") || zuper_method('nested_objects') || []
131
+ end
132
+
125
133
  private #################### PRIVATE METHODS DOWN BELOW ######################
126
134
 
127
135
  def define_nested_objects_getter_methods(nested_objects_list)
@@ -2,13 +2,17 @@ require "object_attorney/orm_handlers/smooth_operator"
2
2
 
3
3
  module ObjectAttorney
4
4
  module ORM
5
+
6
+ def id
7
+ represented_object.try(:id)
8
+ end
5
9
 
6
10
  def new_record?
7
- try_or_return(@represented_object, :new_record?, true)
11
+ try_or_return(represented_object, :new_record?, true)
8
12
  end
9
13
 
10
14
  def persisted?
11
- try_or_return(@represented_object, :persisted?, false)
15
+ try_or_return(represented_object, :persisted?, false)
12
16
  end
13
17
 
14
18
  def save
@@ -23,13 +27,13 @@ module ObjectAttorney
23
27
  end
24
28
 
25
29
  def destroy
26
- return true if @represented_object.blank?
27
- evoke_method_on_object(@represented_object, :destroy)
30
+ return true if represented_object.blank?
31
+ evoke_method_on_object(represented_object, :destroy)
28
32
  end
29
33
 
30
34
  def call_save_or_destroy(object, save_method)
31
35
  if object == self
32
- @represented_object.present? ? evoke_method_on_object(@represented_object, save_method) : true
36
+ represented_object.present? ? evoke_method_on_object(represented_object, save_method) : true
33
37
  else
34
38
  save_method = :destroy if check_if_marked_for_destruction?(object)
35
39
  evoke_method_on_object(object, save_method)
@@ -42,8 +46,18 @@ module ObjectAttorney
42
46
  def after_save; end
43
47
 
44
48
  def save_after_validations(save_method)
45
- return true if @represented_object.blank?
46
- evoke_method_on_object(@represented_object, save_method)
49
+ submit(save_method)
50
+ end
51
+
52
+ def submit(save_method)
53
+ save_result = save_represented_object(save_method)
54
+ save_result = save_nested_objects(save_method) if save_result
55
+ save_result
56
+ end
57
+
58
+ def save_represented_object(save_method)
59
+ return true if represented_object.blank?
60
+ call_save_or_destroy(represented_object, save_method)
47
61
  end
48
62
 
49
63
  private #################### PRIVATE METHODS DOWN BELOW ######################
@@ -15,13 +15,13 @@ module ObjectAttorney
15
15
  end
16
16
 
17
17
  def destroy(options = {})
18
- return true if @represented_object.blank?
19
- evoke_method_on_object(@represented_object, :destroy, options)
18
+ return true if represented_object.blank?
19
+ evoke_method_on_object(represented_object, :destroy, options)
20
20
  end
21
21
 
22
22
  def call_save_or_destroy(object, save_method, options = {})
23
- if object == self || object == @represented_object
24
- @represented_object.present? ? evoke_method_on_object(@represented_object, save_method, options) : true
23
+ if object == self || object == represented_object
24
+ represented_object.present? ? evoke_method_on_object(represented_object, save_method, options) : true
25
25
  else
26
26
  save_method = :destroy if check_if_marked_for_destruction?(object)
27
27
  evoke_method_on_object(object, save_method, options)
@@ -31,8 +31,8 @@ module ObjectAttorney
31
31
  protected #################### PROTECTED METHODS DOWN BELOW ######################
32
32
 
33
33
  def save_after_validations(save_method, options = {})
34
- return true if @represented_object.blank?
35
- evoke_method_on_object(@represented_object, save_method, options).ok?
34
+ return true if represented_object.blank?
35
+ evoke_method_on_object(represented_object, save_method, options).ok?
36
36
  end
37
37
 
38
38
  private #################### PRIVATE METHODS DOWN BELOW ######################
@@ -1,3 +1,3 @@
1
1
  module ObjectAttorney
2
- VERSION = "1.0.2"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require "object_attorney/version"
2
2
  require "object_attorney/nested_objects"
3
3
  require "object_attorney/orm"
4
+ require 'active_record'
4
5
 
5
6
  module ObjectAttorney
6
7
 
@@ -39,21 +40,21 @@ module ObjectAttorney
39
40
 
40
41
  def allowed_attribute(attribute)
41
42
  attribute = attribute.to_s
42
-
43
- return false if !respond_to?("#{attribute}=") || self.class.black_list.include?(attribute)
44
- return true if self.class.white_list.empty?
45
-
46
- self.class.white_list.include?(attribute)
43
+ respond_to?("#{attribute}=")
47
44
  end
48
45
 
49
46
  def validate_represented_object
50
- valid = override_validations? ? true : try_or_return(@represented_object, :valid?, true)
47
+ valid = override_validations? ? true : try_or_return(represented_object, :valid?, true)
51
48
  import_represented_object_errors unless valid
52
49
  valid
53
50
  end
54
51
 
55
52
  def import_represented_object_errors
56
- @represented_object.errors.each { |key, value| self.errors.add(key, value) }
53
+ represented_object.errors.each { |key, value| self.errors.add(key, value) }
54
+ end
55
+
56
+ def represented_object
57
+ @represented_object ||= self.class.represented_object_class.try(:new)
57
58
  end
58
59
 
59
60
  private #################### PRIVATE METHODS DOWN BELOW ######################
@@ -87,36 +88,28 @@ module ObjectAttorney
87
88
 
88
89
  module ClassMethods
89
90
 
90
- def represents(represented_object, represented_object_class = nil)
91
- @@represented_object_class = represented_object_class || represented_object.to_s.camelize.constantize
91
+ def represents(represented_object_name, represented_object_class = nil)
92
+ self.instance_variable_set("@represented_object_class", represented_object_class || represented_object_name.to_s.camelize.constantize)
92
93
 
93
- define_method(represented_object) do
94
- @represented_object ||= @@represented_object_class.new
94
+ define_method(represented_object_name) do
95
+ represented_object
95
96
  end
96
97
  end
97
98
 
98
- def delegate_properties(*properties, options)
99
- properties.each { |property| delegate_propertiy(property, options) }
100
- end
101
-
102
- def delegate_propertiy(property, options)
103
- delegate property, "#{property}=", options
104
- end
105
-
106
- def attr_white_list=(*white_list)
107
- @@white_list = white_list.map(&:to_s)
99
+ def represented_object_class
100
+ self.instance_variable_get("@represented_object_class") || zuper_method('represented_object_class')
108
101
  end
109
102
 
110
- def white_list
111
- @@white_list ||= []
103
+ def zuper_method(method_name, *args)
104
+ self.superclass.send(method_name, *args) if self.superclass.respond_to?(method_name)
112
105
  end
113
106
 
114
- def attr_black_list(*black_list)
115
- @@black_list = black_list.map(&:to_s)
107
+ def delegate_properties(*properties, options)
108
+ properties.each { |property| delegate_property(property, options) }
116
109
  end
117
110
 
118
- def black_list
119
- @@black_list ||= ["_destroy"]
111
+ def delegate_property(property, options)
112
+ delegate property, "#{property}=", options
120
113
  end
121
114
 
122
115
  def human_attribute_name(attribute_key_name, options = {})
@@ -130,19 +123,19 @@ module ObjectAttorney
130
123
 
131
124
  translation = I18n.translate(defaults.shift, options.merge(default: defaults))
132
125
 
133
- if translation == no_translation && @@represented_object_class.respond_to?(:human_attribute_name)
134
- translation = @@represented_object_class.human_attribute_name(attribute_key_name, options)
126
+ if translation == no_translation && represented_object_class.respond_to?(:human_attribute_name)
127
+ translation = represented_object_class.human_attribute_name(attribute_key_name, options)
135
128
  end
136
129
 
137
130
  translation
138
131
  end
139
132
 
140
133
  def model_name
141
- @@_model_name ||= begin
134
+ @_model_name ||= begin
142
135
  namespace = self.parents.detect do |n|
143
136
  n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
144
137
  end
145
- ActiveModel::Name.new(@@represented_object_class || self, namespace)
138
+ ActiveModel::Name.new(represented_object_class || self, namespace)
146
139
  end
147
140
  end
148
141
 
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = ObjectAttorney::VERSION
9
9
  spec.authors = ["João Gonçalves"]
10
10
  spec.email = ["goncalves.joao@gmail.com"]
11
- spec.description = %q{Form Object Patter Implementation}
12
- spec.summary = %q{This gem allows you to extract the code responsible for Validations, Nested Objects and Forms, from your model, into a specific class for a specific use case.}
11
+ spec.description = %q{Form Object pattern implementation for Rails}
12
+ spec.summary = %q{This gem allows you to extract the code responsible for 'validations', 'nested objects' and 'strong parameters' from your model onto a specific class for a specific use case.}
13
13
  spec.homepage = "https://github.com/goncalvesjoao/object_attorney"
14
14
  spec.license = "MIT"
15
15
 
@@ -0,0 +1,191 @@
1
+ require "spec_helper"
2
+
3
+ describe BulkPostsFormChild do
4
+
5
+ it "Creating multiple Posts, with a tabless model 'BulkPostsFormChild' has if it had 'accepts_nested_attributes_for :posts'" do
6
+ params = {
7
+ bulk_post: {
8
+ admin: true,
9
+ posts_attributes: {
10
+ "0" => { state: "draft", title: "My title1" },
11
+ "1" => { state: "public", title: "My title2" }
12
+ }
13
+ }
14
+ }
15
+
16
+ bulk_posts_form_child = BulkPostsFormChild.new(params[:bulk_post])
17
+ bulk_posts_form_child.save
18
+
19
+ expect(Post.all.count).to(eq(2))
20
+ end
21
+
22
+ it "Trying to create multiple Posts, with the same title (testing the 'validates_nested_uniqueness')" do
23
+ params = {
24
+ bulk_post: {
25
+ admin: true,
26
+ posts_attributes: {
27
+ "0" => { state: "draft", title: "My title1" },
28
+ "1" => { state: "public", title: "My title1" }
29
+ }
30
+ }
31
+ }
32
+
33
+ bulk_posts_form = BulkPostsFormChild.new(params[:bulk_post])
34
+ bulk_posts_form.save
35
+
36
+ # TODO: Ensure that the nested objects remember their respective errors
37
+ # see: http://stackoverflow.com/questions/13879700/rails-model-valid-flusing-custom-errors-and-falsely-returning-true
38
+
39
+ expect(Post.all.count).to(eq(0))
40
+ end
41
+
42
+ it "Creating new Post and editing an existing one" do
43
+ params = {
44
+ bulk_post: {
45
+ admin: true,
46
+ posts_attributes: {
47
+ "0" => { id: 1, state: "draft", title: "Changed title" },
48
+ "1" => { state: "public", title: "My title2" }
49
+ }
50
+ }
51
+ }
52
+
53
+ existing_post = Post.create(title: "My title1")
54
+ BulkPostsFormChild.new(params[:bulk_post]).save
55
+ existing_post.reload
56
+
57
+ expect(Post.all.count).to(eq(2)) && expect(existing_post.title).to(eq('Changed title'))
58
+ end
59
+
60
+ it "Creating new Post and deleting an existing one" do
61
+ params = {
62
+ bulk_post: {
63
+ admin: true,
64
+ posts_attributes: {
65
+ "0" => { id: 1, state: "draft", title: "Changed title", _destroy: true },
66
+ "1" => { state: "public", title: "My title2" }
67
+ }
68
+ }
69
+ }
70
+
71
+ existing_post = Post.create(title: "My title1")
72
+ BulkPostsFormChild.new(params[:bulk_post]).save
73
+
74
+ expect(Post.all.count).to(eq(1)) && expect(Post.where(id: existing_post.id).present?).to(eq(false))
75
+ end
76
+
77
+ it "Trying to create multiple Posts, but one of them is invalid" do
78
+ params = {
79
+ bulk_post: {
80
+ admin: true,
81
+ posts_attributes: {
82
+ "0" => { title: "My title1" },
83
+ "1" => { state: "public", title: "My title2" }
84
+ }
85
+ }
86
+ }
87
+
88
+ BulkPostsFormChild.new(params[:bulk_post]).save
89
+
90
+ params = {
91
+ bulk_post: {
92
+ admin: true,
93
+ posts_attributes: {
94
+ "0" => { state: 'draft', title: "My title1" },
95
+ "1" => { state: "public" }
96
+ }
97
+ }
98
+ }
99
+
100
+ BulkPostsFormChild.new(params[:bulk_post]).save
101
+
102
+ expect(Post.all.count).to(eq(0))
103
+ end
104
+
105
+ it "Trying to create new Post and editing an existing one, but one of them is invalid" do
106
+ params = {
107
+ bulk_post: {
108
+ admin: true,
109
+ posts_attributes: {
110
+ "0" => { id: 1, title: "Changed title" },
111
+ "1" => { state: "public", title: "My title2" }
112
+ }
113
+ }
114
+ }
115
+
116
+ existing_post = Post.create(title: "My title1")
117
+ BulkPostsFormChild.new(params[:bulk_post]).save
118
+ existing_post.reload
119
+
120
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
121
+
122
+ params = {
123
+ bulk_post: {
124
+ admin: true,
125
+ posts_attributes: {
126
+ "0" => { id: 1, state: 'draft', title: "Changed title" },
127
+ "1" => { state: "public" }
128
+ }
129
+ }
130
+ }
131
+
132
+ BulkPostsFormChild.new(params[:bulk_post]).save
133
+ existing_post.reload
134
+
135
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
136
+ end
137
+
138
+ it "Trying to create new Post and deleting an existing one, but the new one is invalid" do
139
+ params = {
140
+ bulk_post: {
141
+ admin: true,
142
+ posts_attributes: {
143
+ "0" => { id: 1, state: "draft", title: "Changed title", _destroy: true },
144
+ "1" => { state: "public" }
145
+ }
146
+ }
147
+ }
148
+
149
+ existing_post = Post.create(title: "My title1")
150
+ BulkPostsFormChild.new(params[:bulk_post]).save
151
+ existing_post.reload
152
+
153
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
154
+ end
155
+
156
+ it "Trying to create new Post and deleting an existing one, the existing one is invalid but since it is marked for destruction, it should be deleted" do
157
+ params = {
158
+ bulk_post: {
159
+ admin: true,
160
+ posts_attributes: {
161
+ "0" => { id: 1, title: "Changed title", _destroy: true },
162
+ "1" => { state: "public", title: "My title2" }
163
+ }
164
+ }
165
+ }
166
+
167
+ existing_post = Post.create(title: "My title1")
168
+ bulk_posts_form_child = BulkPostsFormChild.new(params[:bulk_post])
169
+ bulk_posts_form_child.save
170
+
171
+ expect(Post.all.count).to(eq(1)) && expect(Post.where(id: existing_post.id).present?).to(eq(false))
172
+ end
173
+
174
+ it "Trying to create new Post and deleting an existing one, both of them are invalid, no changes should occur." do
175
+ params = {
176
+ bulk_post: {
177
+ admin: true,
178
+ posts_attributes: {
179
+ "0" => { id: 1, title: "Changed title", _destroy: true },
180
+ "1" => { state: "public" }
181
+ }
182
+ }
183
+ }
184
+
185
+ existing_post = Post.create(title: "My title1")
186
+ BulkPostsFormChild.new(params[:bulk_post]).save
187
+
188
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
189
+ end
190
+
191
+ end
@@ -0,0 +1,178 @@
1
+ require "spec_helper"
2
+
3
+ describe BulkPostsForm do
4
+
5
+ it "Creating multiple Posts, with a tabless model 'BulkPostsForm' has if it had 'accepts_nested_attributes_for :posts'" do
6
+ params = {
7
+ bulk_post: {
8
+ posts_attributes: {
9
+ "0" => { state: "draft", title: "My title1" },
10
+ "1" => { state: "public", title: "My title2" }
11
+ }
12
+ }
13
+ }
14
+
15
+ BulkPostsForm.new(params[:bulk_post]).save
16
+
17
+ expect(Post.all.count).to(eq(2))
18
+ end
19
+
20
+ it "Creating multiple Posts, with the same title (testing the 'validates_nested_uniqueness')" do
21
+ params = {
22
+ bulk_post: {
23
+ posts_attributes: {
24
+ "0" => { state: "draft", title: "My title1" },
25
+ "1" => { state: "public", title: "My title1" }
26
+ }
27
+ }
28
+ }
29
+
30
+ bulk_posts_form = BulkPostsForm.new(params[:bulk_post])
31
+ bulk_posts_form.save
32
+
33
+ # TODO: Ensure that the nested objects remember their respective errors
34
+ # see: http://stackoverflow.com/questions/13879700/rails-model-valid-flusing-custom-errors-and-falsely-returning-true
35
+
36
+ expect(Post.all.count).to(eq(0))
37
+ end
38
+
39
+ it "Creating new Post and editing an existing one" do
40
+ params = {
41
+ bulk_post: {
42
+ posts_attributes: {
43
+ "0" => { id: 1, state: "draft", title: "Changed title" },
44
+ "1" => { state: "public", title: "My title2" }
45
+ }
46
+ }
47
+ }
48
+
49
+ existing_post = Post.create(title: "My title1")
50
+ BulkPostsForm.new(params[:bulk_post]).save
51
+ existing_post.reload
52
+
53
+ expect(Post.all.count).to(eq(2)) && expect(existing_post.title).to(eq('Changed title'))
54
+ end
55
+
56
+ it "Creating new Post and deleting an existing one" do
57
+ params = {
58
+ bulk_post: {
59
+ posts_attributes: {
60
+ "0" => { id: 1, state: "draft", title: "Changed title", _destroy: true },
61
+ "1" => { state: "public", title: "My title2" }
62
+ }
63
+ }
64
+ }
65
+
66
+ existing_post = Post.create(title: "My title1")
67
+ BulkPostsForm.new(params[:bulk_post]).save
68
+
69
+ expect(Post.all.count).to(eq(1)) && expect(Post.where(id: existing_post.id).present?).to(eq(false))
70
+ end
71
+
72
+ it "Trying to create multiple Posts, but one of them is invalid" do
73
+ params = {
74
+ bulk_post: {
75
+ posts_attributes: {
76
+ "0" => { title: "My title1" },
77
+ "1" => { state: "public", title: "My title2" }
78
+ }
79
+ }
80
+ }
81
+
82
+ BulkPostsForm.new(params[:bulk_post]).save
83
+
84
+ params = {
85
+ bulk_post: {
86
+ posts_attributes: {
87
+ "0" => { state: 'draft', title: "My title1" },
88
+ "1" => { state: "public" }
89
+ }
90
+ }
91
+ }
92
+
93
+ BulkPostsForm.new(params[:bulk_post]).save
94
+
95
+ expect(Post.all.count).to(eq(0))
96
+ end
97
+
98
+ it "Trying to create new Post and editing an existing one, but one of them is invalid" do
99
+ params = {
100
+ bulk_post: {
101
+ posts_attributes: {
102
+ "0" => { id: 1, title: "Changed title" },
103
+ "1" => { state: "public", title: "My title2" }
104
+ }
105
+ }
106
+ }
107
+
108
+ existing_post = Post.create(title: "My title1")
109
+ BulkPostsForm.new(params[:bulk_post]).save
110
+ existing_post.reload
111
+
112
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
113
+
114
+ params = {
115
+ bulk_post: {
116
+ posts_attributes: {
117
+ "0" => { id: 1, state: 'draft', title: "Changed title" },
118
+ "1" => { state: "public" }
119
+ }
120
+ }
121
+ }
122
+
123
+ BulkPostsForm.new(params[:bulk_post]).save
124
+ existing_post.reload
125
+
126
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
127
+ end
128
+
129
+ it "Trying to create new Post and deleting an existing one, but the new one is invalid" do
130
+ params = {
131
+ bulk_post: {
132
+ posts_attributes: {
133
+ "0" => { id: 1, state: "draft", title: "Changed title", _destroy: true },
134
+ "1" => { state: "public" }
135
+ }
136
+ }
137
+ }
138
+
139
+ existing_post = Post.create(title: "My title1")
140
+ BulkPostsForm.new(params[:bulk_post]).save
141
+ existing_post.reload
142
+
143
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
144
+ end
145
+
146
+ it "Trying to create new Post and deleting an existing one, the existing one is invalid but since it is marked for destruction, it should be deleted" do
147
+ params = {
148
+ bulk_post: {
149
+ posts_attributes: {
150
+ "0" => { id: 1, title: "Changed title", _destroy: true },
151
+ "1" => { state: "public", title: "My title2" }
152
+ }
153
+ }
154
+ }
155
+
156
+ existing_post = Post.create(title: "My title1")
157
+ BulkPostsForm.new(params[:bulk_post]).save
158
+
159
+ expect(Post.all.count).to(eq(1)) && expect(Post.where(id: existing_post.id).present?).to(eq(false))
160
+ end
161
+
162
+ it "Trying to create new Post and deleting an existing one, both of them are invalid, no changes should occur." do
163
+ params = {
164
+ bulk_post: {
165
+ posts_attributes: {
166
+ "0" => { id: 1, title: "Changed title", _destroy: true },
167
+ "1" => { state: "public" }
168
+ }
169
+ }
170
+ }
171
+
172
+ existing_post = Post.create(title: "My title1")
173
+ BulkPostsForm.new(params[:bulk_post]).save
174
+
175
+ expect(Post.all.count).to(eq(1)) && expect(existing_post.title).to(eq('My title1'))
176
+ end
177
+
178
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe PostFormChild do
4
+
5
+ it "PostFormChild becomes invalid when Post does and incorporates its errors" do
6
+ post = Post.new
7
+ post.should have(1).error_on(:title)
8
+ post.title = "My title"
9
+ post.should have(:no).errors_on(:title)
10
+
11
+ post_form = PostFormChild.new({ state: 'draft', date: Date.today })
12
+ post_form.should have(1).error_on(:title)
13
+ post_form.title = "My title"
14
+ post_form.should have(:no).errors_on(:title)
15
+ end
16
+
17
+ it "PostFormChild may require the validations of fields that Post doesn't have" do
18
+ params = { post: { title: "My title" } }
19
+
20
+ post = Post.new(params[:post])
21
+ post.should have(:no).errors_on(:date)
22
+
23
+ post_form = PostFormChild.new(params[:post].merge(state: 'public'))
24
+ post_form.should have(1).error_on(:date)
25
+ post_form.date = Date.today
26
+ post_form.should have(:no).errors_on(:date)
27
+ end
28
+
29
+ it "Post creation through PostFormChild" do
30
+ params = { post: { state: 'public', title: "My title", body: "My body", date: Date.today } }
31
+ post_form = PostFormChild.new(params[:post])
32
+
33
+ expect(post_form.save).to(eq(true)) && expect(post_form.post.persisted?).to(eq(true))
34
+ end
35
+
36
+ it "Post can't be created if PostFormChild isn't valid" do
37
+ params = { post: { state: 'public', title: "My title", body: "My body" } }
38
+ post_form = PostFormChild.new(params[:post])
39
+
40
+ expect(post_form.save).to(eq(false)) && expect(post_form.post.persisted?).to(eq(false))
41
+ end
42
+
43
+ it "Post can't be created if Post isn't valid" do
44
+ params = { post: { state: 'public', date: Date.today, body: "My body" } }
45
+ post_form = PostFormChild.new(params[:post])
46
+
47
+ expect(post_form.save).to(eq(false)) && expect(post_form.post.persisted?).to(eq(false))
48
+ end
49
+
50
+ it "PostFormChild won't allow weak params to be updated, unlike Post" do
51
+ params = { post: { title: 'My title', body: "My body", admin: true } }
52
+
53
+ post_form = PostFormChild.new(params[:post].merge({ state: 'public', date: Date.today }))
54
+ expect(post_form.save).to(eq(true)) && expect(post_form.post.admin).to(eq(false))
55
+
56
+ post = Post.new(params[:post])
57
+ expect(post.save).to(eq(true)) && expect(post.admin).to(eq(true))
58
+ end
59
+
60
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe PostForm do
4
+
5
+ it "PostForm becomes invalid when Post does and incorporates its errors" do
6
+ post = Post.new
7
+ post.should have(1).error_on(:title)
8
+ post.title = "My title"
9
+ post.should have(:no).errors_on(:title)
10
+
11
+ post_form = PostForm.new({ state: 'draft' })
12
+ post_form.should have(1).error_on(:title)
13
+ post_form.title = "My title"
14
+ post_form.should have(:no).errors_on(:title)
15
+ end
16
+
17
+ it "PostForm may require the validations of fields that Post doesn't have" do
18
+ params = { post: { title: "My title" } }
19
+
20
+ post = Post.new(params[:post])
21
+ post.should have(:no).errors_on(:state)
22
+
23
+ post_form = PostForm.new(params[:post])
24
+ post_form.should have(1).error_on(:state)
25
+ post_form.state = "draft"
26
+ post_form.should have(:no).errors_on(:state)
27
+ end
28
+
29
+ it "Post creation through PostForm" do
30
+ params = { post: { state: 'public', title: "My title", body: "My body" } }
31
+ post_form = PostForm.new(params[:post])
32
+
33
+ expect(post_form.save).to(eq(true)) && expect(post_form.post.persisted?).to(eq(true))
34
+ end
35
+
36
+ it "Post can't be created if PostForm isn't valid" do
37
+ params = { post: { title: "My title", body: "My body" } }
38
+ post_form = PostForm.new(params[:post])
39
+
40
+ expect(post_form.save).to(eq(false)) && expect(post_form.post.persisted?).to(eq(false))
41
+ end
42
+
43
+ it "Post can't be created if Post isn't valid" do
44
+ params = { post: { state: 'public', body: "My body" } }
45
+ post_form = PostForm.new(params[:post])
46
+
47
+ expect(post_form.save).to(eq(false)) && expect(post_form.post.persisted?).to(eq(false))
48
+ end
49
+
50
+ it "PostForm won't allow weak params to be updated, unlike Post" do
51
+ params = { post: { title: 'My title', body: "My body", admin: true } }
52
+
53
+ post_form = PostForm.new(params[:post].merge({ state: 'public' }))
54
+ expect(post_form.save).to(eq(true)) && expect(post_form.post.admin).to(eq(false))
55
+
56
+ post = Post.new(params[:post])
57
+ expect(post.save).to(eq(true)) && expect(post.admin).to(eq(true))
58
+ end
59
+
60
+ end
@@ -0,0 +1,47 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ ENV["RAILS_ENV"] ||= 'test'
5
+
6
+ require 'bundler'
7
+ Bundler.setup
8
+ require 'rspec'
9
+ require 'pry'
10
+ #require 'database_cleaner'
11
+
12
+ require 'object_attorney'
13
+ require 'support/database_setup'
14
+ require 'support/active_model/validations'
15
+ require 'support/models/post'
16
+ require 'support/models/post_form'
17
+ require 'support/models/post_form_child'
18
+ require 'support/models/bulk_posts_form'
19
+ require 'support/models/bulk_posts_form_child'
20
+
21
+ RSpec.configure do |config|
22
+
23
+ I18n.enforce_available_locales = false
24
+
25
+ # see: http://iain.nl/testing-activerecord-in-isolation
26
+ config.around do |example|
27
+ ActiveRecord::Base.transaction do
28
+ example.run
29
+ raise ActiveRecord::Rollback
30
+ end
31
+ end
32
+
33
+ # see: https://github.com/bmabey/database_cleaner#rspec-example
34
+ # config.before(:suite) do
35
+ # DatabaseCleaner.strategy = :transaction
36
+ # DatabaseCleaner.clean_with(:truncation)
37
+ # end
38
+
39
+ # config.before(:each) do
40
+ # DatabaseCleaner.start
41
+ # end
42
+
43
+ # config.after(:each) do
44
+ # DatabaseCleaner.clean
45
+ # end
46
+
47
+ end
@@ -0,0 +1,21 @@
1
+
2
+ # see: http://iain.nl/testing-activerecord-in-isolation
3
+ module ActiveModel::Validations
4
+ # Extension to enhance `should have` on AR Model instances. Calls
5
+ # model.valid? in order to prepare the object's errors object.
6
+ #
7
+ # You can also use this to specify the content of the error messages.
8
+ #
9
+ # @example
10
+ #
11
+ # model.should have(:no).errors_on(:attribute)
12
+ # model.should have(1).error_on(:attribute)
13
+ # model.should have(n).errors_on(:attribute)
14
+ #
15
+ # model.errors_on(:attribute).should include("can't be blank")
16
+ def errors_on(attribute)
17
+ self.valid?
18
+ [self.errors[attribute]].flatten.compact
19
+ end
20
+ alias :error_on :errors_on
21
+ end
@@ -0,0 +1,14 @@
1
+ # see: http://iain.nl/testing-activerecord-in-isolation
2
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
3
+ ActiveRecord::Migrator.up "db/migrate"
4
+
5
+ # see: http://blog.aizatto.com/2007/05/27/activerecord-migrations-without-rails/
6
+ #ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))[ENV["RAILS_ENV"]])
7
+ #ActiveRecord::Base.logger = Logger.new(File.open('tmp/database.log', 'a'))
8
+
9
+ # config/database.yml
10
+ # test:
11
+ # adapter: sqlite3
12
+ # database: db/test.sqlite3
13
+ # pool: 5
14
+ # timeout: 5000
@@ -0,0 +1,21 @@
1
+ require 'object_attorney/nested_uniqueness_validator'
2
+
3
+ class BulkPostsForm
4
+
5
+ include ObjectAttorney
6
+
7
+ accepts_nested_objects :posts
8
+
9
+ validates_nested_uniqueness :posts, uniq_value: :title
10
+
11
+ ##################### BODY BELLOW THIS LINE ####################
12
+
13
+ def build_post(attributes = {}, post = nil)
14
+ PostForm.new(attributes, post)
15
+ end
16
+
17
+ def existing_posts
18
+ Post.all.map { |post| build_post({}, post) }
19
+ end
20
+
21
+ end
@@ -0,0 +1,7 @@
1
+ class BulkPostsFormChild < BulkPostsForm
2
+
3
+ attr_accessor :admin
4
+
5
+ validates_presence_of :admin
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ class Post < ActiveRecord::Base
2
+ validates_presence_of :title
3
+ end
@@ -0,0 +1,13 @@
1
+ class PostForm
2
+
3
+ include ObjectAttorney
4
+
5
+ represents :post, Post
6
+
7
+ delegate_properties :title, :body, to: :post
8
+
9
+ attr_accessor :state
10
+
11
+ validates_presence_of :state
12
+
13
+ end
@@ -0,0 +1,7 @@
1
+ class PostFormChild < PostForm
2
+
3
+ attr_accessor :date
4
+
5
+ validates_presence_of :date
6
+
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: object_attorney
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Gonçalves
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-04 00:00:00.000000000 Z
11
+ date: 2013-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description: Form Object Patter Implementation
41
+ description: Form Object pattern implementation for Rails
42
42
  email:
43
43
  - goncalves.joao@gmail.com
44
44
  executables: []
@@ -46,10 +46,15 @@ extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
48
  - .gitignore
49
+ - .rspec
50
+ - .rvmrc
49
51
  - Gemfile
52
+ - Guardfile
50
53
  - LICENSE.txt
51
54
  - README.md
52
55
  - Rakefile
56
+ - db/migrate/20131205114900_create_posts.rb
57
+ - db/schema.rb
53
58
  - lib/object_attorney.rb
54
59
  - lib/object_attorney/nested_objects.rb
55
60
  - lib/object_attorney/nested_uniqueness_validator.rb
@@ -57,6 +62,18 @@ files:
57
62
  - lib/object_attorney/orm_handlers/smooth_operator.rb
58
63
  - lib/object_attorney/version.rb
59
64
  - object_attorney.gemspec
65
+ - spec/object_attorney/bulk_posts_form_child_spec.rb
66
+ - spec/object_attorney/bulk_posts_form_spec.rb
67
+ - spec/object_attorney/post_form_child_spec.rb
68
+ - spec/object_attorney/post_form_spec.rb
69
+ - spec/spec_helper.rb
70
+ - spec/support/active_model/validations.rb
71
+ - spec/support/database_setup.rb
72
+ - spec/support/models/bulk_posts_form.rb
73
+ - spec/support/models/bulk_posts_form_child.rb
74
+ - spec/support/models/post.rb
75
+ - spec/support/models/post_form.rb
76
+ - spec/support/models/post_form_child.rb
60
77
  homepage: https://github.com/goncalvesjoao/object_attorney
61
78
  licenses:
62
79
  - MIT
@@ -80,7 +97,19 @@ rubyforge_project:
80
97
  rubygems_version: 2.1.10
81
98
  signing_key:
82
99
  specification_version: 4
83
- summary: This gem allows you to extract the code responsible for Validations, Nested
84
- Objects and Forms, from your model, into a specific class for a specific use case.
85
- test_files: []
86
- has_rdoc:
100
+ summary: This gem allows you to extract the code responsible for 'validations', 'nested
101
+ objects' and 'strong parameters' from your model onto a specific class for a specific
102
+ use case.
103
+ test_files:
104
+ - spec/object_attorney/bulk_posts_form_child_spec.rb
105
+ - spec/object_attorney/bulk_posts_form_spec.rb
106
+ - spec/object_attorney/post_form_child_spec.rb
107
+ - spec/object_attorney/post_form_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/support/active_model/validations.rb
110
+ - spec/support/database_setup.rb
111
+ - spec/support/models/bulk_posts_form.rb
112
+ - spec/support/models/bulk_posts_form_child.rb
113
+ - spec/support/models/post.rb
114
+ - spec/support/models/post_form.rb
115
+ - spec/support/models/post_form_child.rb