object_attorney 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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