rfo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 78405d779a711a4e000a04139452abf7c925e5dc
4
+ data.tar.gz: 9c068a61f479eb683c19d1252c645592a0ebbef6
5
+ SHA512:
6
+ metadata.gz: d4a2951101ce4ae762accdb469a02a59aa27637a5777b0641fa13b6fc880e2bccba68ed049c13e685bcfd6a78035d200438733c444b696fe5e806e1b3b3e7df8
7
+ data.tar.gz: cda9e033aa33849d4d12dc5f26c7817b66e8edac28e4b7d66ea7a5dc98535e96ce595263503a1258c2f142012c5a64d4c9a0cededc868744cd42b7837b9a7f46
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rfo.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Piotr Gębala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Piotr Gębala
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,254 @@
1
+ [![Code Climate](https://codeclimate.com/github/petergebala/rfo.png)](https://codeclimate.com/github/petergebala/rfo)
2
+ # RFO - Rails Form Object
3
+
4
+ Inspired by [7 Patterns to Refactor Fat ActiveRecord Models] (http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) and [SRP](http://en.wikipedia.org/wiki/Single_responsibility_principle)
5
+
6
+ What it does?
7
+ + simplify forms with multiple models - we do not have to use nested attributes
8
+ + remove validations from models - they do too much already. What is more each form can have different validations.
9
+ + can substitude strong parameters - we define exacly what and how we want to assign values in models
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'rfo', github: 'petergebala/rfo'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install rfo # Not yet!
24
+
25
+ ## Usage
26
+ It works great with [Draper](http://github.com/drapergem/draper) and [Simple Form](https://github.com/plataformatec/simple_form)
27
+
28
+ Define simple model with relations eventually callbacks:
29
+ ```ruby
30
+ class Organisation < ActiveRecord::Base
31
+ WEBSITE_REGEXP = /[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?/ix
32
+ APPLY_FOR = [:under_2000, :over_2000]
33
+
34
+ belongs_to :grant, touch: true
35
+ has_one :address, as: :entity, dependent: :destroy
36
+ has_one :contact, as: :entity, dependent: :destroy
37
+ has_one :user, through: :grant
38
+
39
+ after_create :create_address
40
+ after_create :create_contact
41
+
42
+ delegate :postcode, to: :address
43
+ end
44
+ ```
45
+
46
+ Define form:
47
+ + fields which you will use in form
48
+ + validations for this form
49
+ + default values
50
+ + how and where it should assign values
51
+
52
+ ```ruby
53
+ # app/forms/organisation_form.rb
54
+ class OrganisationForm < RFO::Base
55
+ # About your organisation
56
+ attribute :amount_apply_for, String
57
+ attribute :organisation_name, String
58
+ attribute :charity_number, String
59
+
60
+ # Your address details
61
+ attribute :first_name, String
62
+ attribute :last_name, String
63
+ attribute :position, String
64
+ attribute :address_line_1, String
65
+ attribute :address_line_2, String
66
+ attribute :address_line_3, String
67
+ attribute :town, String
68
+ attribute :county, String
69
+ attribute :postcode, String
70
+
71
+ # Your contact details
72
+ attribute :phone_number, String
73
+ attribute :mobile_number, String
74
+
75
+ # Your organisation
76
+ attribute :website, String
77
+ attribute :description, String
78
+
79
+ # Validations
80
+ validates :description, presence: true, length: { maximum: 2000 }
81
+ validates :amount_apply_for, presence: true, inclusion: { in: Organisation::APPLY_FOR.map(&:to_s) }
82
+ validates :organisation_name, presence: true, length: { maximum: 255 }
83
+ validates :first_name, presence: true, length: { maximum: 255 }
84
+ validates :last_name, presence: true, length: { maximum: 255 }
85
+ validates :address_line_1, presence: true, length: { maximum: 255 }
86
+ validates :town, presence: true, length: { maximum: 255 }
87
+ validates :postcode, presence: true, length: { maximum: 10 }, format: { with: Address::POSTCODE_REGEXP }
88
+ validates :phone_number, presence: true, length: { maximum: 20 }, format: { with: Contact::PHONE_NUMBER_REGEXP }
89
+ validates :mobile_number, length: { maximum: 20 }, format: { with: Contact::PHONE_NUMBER_REGEXP }, allow_blank: true
90
+ validates :website, length: { maximum: 255 }, format: { with: Organisation::WEBSITE_REGEXP }, allow_blank: true
91
+
92
+ private
93
+ def assign_defaults
94
+ @grant.current_step = :organisation_details
95
+
96
+ organisation_contact = @organisation.contact
97
+ organisation_address = @organisation.address
98
+ user_contact = @organisation.user.contact
99
+ user_address = @organisation.user.address
100
+
101
+ # Initialize with values from diffrent models
102
+ self.organisation_name ||= @organisation.name
103
+ self.first_name ||= organisation_contact.first_name || user_contact.first_name
104
+ self.last_name ||= organisation_contact.last_name || user_contact.last_name
105
+ self.position ||= organisation_contact.position || user_contact.position
106
+ self.phone_number ||= organisation_contact.phone_number || user_contact.phone_number
107
+ self.mobile_number ||= organisation_contact.mobile_number || user_contact.mobile_number
108
+ self.address_line_1 ||= organisation_address.address_line_1 || user_address.address_line_1
109
+ self.address_line_2 ||= organisation_address.address_line_2 || user_address.address_line_2
110
+ self.address_line_3 ||= organisation_address.address_line_3 || user_address.address_line_3
111
+ self.town ||= organisation_address.town || user_address.town
112
+ self.county ||= organisation_address.county || user_address.county
113
+ self.postcode ||= organisation_address.postcode || user_address.postcode
114
+ end
115
+
116
+ def persist!
117
+ ActiveRecord::Base.transaction do |t|
118
+ @organisation.name = self.organisation_name
119
+ @organisation.amount_apply_for = self.amount_apply_for
120
+ @organisation.charity_number = self.charity_number
121
+ @organisation.website = self.website
122
+ @organisation.description = self.description
123
+
124
+ @contact = @organisation.contact
125
+ @contact.first_name = self.first_name
126
+ @contact.last_name = self.last_name
127
+ @contact.position = self.position
128
+ @contact.phone_number = self.phone_number
129
+ @contact.mobile_number = self.mobile_number
130
+
131
+ @address = @organisation.address
132
+ @address.address_line_1 = self.address_line_1
133
+ @address.address_line_2 = self.address_line_2
134
+ @address.address_line_3 = self.address_line_3
135
+ @address.town = self.town
136
+ @address.county = self.county
137
+ @address.postcode = self.postcode
138
+
139
+ @grant.current_step = @grant.next_step
140
+
141
+ @organisation.save!
142
+ @contact.save!
143
+ @address.save!
144
+ @grant.save!
145
+ end
146
+ end
147
+ end
148
+ ```
149
+
150
+ Define skinny controller:
151
+ ```ruby
152
+ class OrganisationDetailsController < ApplicationController
153
+ before_filter :set_grant
154
+ respond_to :html
155
+
156
+ before_filter :set_organisation, only: [:edit, :update]
157
+
158
+ def edit ; end
159
+
160
+ def update
161
+ flash[:notice] = 'Organisation details saved!' if @organisation_form.update_attributes(organisations_params)
162
+ respond_with @organisation_form, location: @grant.current_path
163
+ end
164
+
165
+ private
166
+ def set_grant
167
+ @grant = current_user.grants.find(params[:grant_id]).decorate
168
+ end
169
+
170
+ def set_organisation
171
+ @organisation = @grant.organisation.decorate
172
+ @organisation_form = OrganisationForm.new(organisation: @organisation,
173
+ grant: @grant)
174
+ end
175
+
176
+ def organisations_params
177
+ params.require(:organisation_form).permit(:amount_apply_for,
178
+ :organisation_name,
179
+ :charity_number,
180
+ :first_name,
181
+ :last_name,
182
+ :position,
183
+ :address_line_1,
184
+ :address_line_2,
185
+ :address_line_3,
186
+ :town,
187
+ :county,
188
+ :postcode,
189
+ :phone_number,
190
+ :mobile_number,
191
+ :website,
192
+ :description)
193
+ end
194
+ end
195
+ ```
196
+
197
+ And clean view!
198
+ ```haml
199
+ = simple_form_for @organisation_form, url: grant_organisation_details_path(@grant), method: :patch do |f|
200
+ = f.error_notification
201
+ h2 About Your Organisation
202
+ fieldset
203
+ = f.input :amount_apply_for,
204
+ collection: @organisation.amount_apply_for_buttons_for_views,
205
+ as: :radio_buttons,
206
+ wrapper: :bootstrap_group_horizontal
207
+ = f.input :organisation_name
208
+ = f.input :charity_number
209
+ h2 Your Address Details
210
+ fieldset
211
+ = f.input :first_name
212
+ = f.input :last_name
213
+ = f.input :position
214
+ = f.input :address_line_1
215
+ = f.input :address_line_2
216
+ = f.input :address_line_3
217
+ = f.input :town
218
+ = f.input :county
219
+ = f.input :postcode
220
+ h2 Your Contact Details
221
+ fieldset
222
+ = f.input :phone_number
223
+ = f.input :mobile_number
224
+ h2 Your Organisation
225
+ fieldset
226
+ = f.input :website, as: :addon, input_html: { addon_text: 'http://' }
227
+ = f.input :description, as: :text
228
+ h2 Proceed
229
+ fieldset
230
+ = f.button :submit
231
+ ```
232
+
233
+ ## Additional information
234
+
235
+ Becasue Form Object is just plain ruby class you can:
236
+ + test it in simple way,
237
+ + share common code (like validations) between form objects,
238
+ + inherit between forms.
239
+
240
+
241
+ ## Contributing
242
+
243
+ 1. Fork it
244
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
245
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
246
+ 4. Push to the branch (`git push origin my-new-feature`)
247
+ 5. Create new Pull Request
248
+
249
+ ## TODO:
250
+
251
+ - turn off strong_parameters
252
+ - write tests
253
+ - correct documentation
254
+ - update documentation with has_many relation and show how to remove nested_attributes
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ require 'virtus'
2
+
3
+ module RFO
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'rfo/base'
8
+ require 'rfo/version'
@@ -0,0 +1,97 @@
1
+ ##
2
+ # This is a main Form Object class
3
+ class RFO::Base
4
+ include Virtus.model
5
+
6
+ if Rails.version > '4.1'
7
+ include ActiveModel::Model
8
+ else
9
+ extend ActiveModel::Naming
10
+ include ActiveModel::Conversion
11
+ include ActiveModel::Validations
12
+ end
13
+
14
+ ##
15
+ # Constructor:
16
+ # ==== Arguments
17
+ # * hash - where key is a name and value is an object. If key is an AR object or Draper object then key name will become instance variable with value defined in hash value.
18
+ #
19
+ # Run also +assign_defaults+ method to set default values
20
+ def initialize(*args)
21
+ options = args.extract_options!
22
+ options.each_pair do |name, obj|
23
+ next unless active_record_object?(obj)
24
+ instance_variable_set("@#{name}", obj)
25
+ super obj.attributes.symbolize_keys.slice(*self.attributes.keys)
26
+ end
27
+ assign_defaults
28
+ end
29
+
30
+ ##
31
+ # We can specify Form Object to act as some class
32
+ #
33
+ # ==== Example
34
+ #
35
+ # # Form Object
36
+ # class SomeForm < RFO::Base
37
+ # model_class Foo
38
+ #
39
+ # attribute :name, String
40
+ # end
41
+ #
42
+ # # In view we can use it to generate default paths in form.
43
+ # = simple_form_for @some_form do |f|
44
+ # = f.input :name
45
+ # = f.submit
46
+ #
47
+ def self.model_class(name)
48
+ @model_name = name || self
49
+ end
50
+
51
+ ##
52
+ # Required for defining paths in rails
53
+ #
54
+ def self.model_name
55
+ @model_name ||= self
56
+ ActiveModel::Name.new(@model_name, nil)
57
+ end
58
+
59
+ def persisted?
60
+ false
61
+ end
62
+
63
+ def save
64
+ if valid?
65
+ persist!
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ def update_attributes(attributes_hash)
72
+ self.attributes = attributes_hash
73
+ self.save
74
+ end
75
+
76
+ protected
77
+ ##
78
+ # Method which will be overwritten in subclasses
79
+ #
80
+ def assign_defaults
81
+ end
82
+
83
+ private
84
+ def active_record_object?(record)
85
+ # Pure AR Object
86
+ return true if pure_active_record?(record)
87
+
88
+ # Decorated Object
89
+ return true if record.respond_to?(:source) && pure_active_record?(record.source)
90
+
91
+ false
92
+ end
93
+
94
+ def pure_active_record?(record)
95
+ record.class.ancestors.include?(ActiveRecord::Base)
96
+ end
97
+ end
@@ -0,0 +1,3 @@
1
+ module RFO
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rfo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rfo"
8
+ spec.version = RFO::VERSION
9
+ spec.authors = ["Piotr Gębala", "Bartłomiej Oleszczyk"]
10
+ spec.email = ["piotrek.gebala@gmail.com", "bart@primate.co.uk"]
11
+ spec.description = %q{Rails Form Object}
12
+ spec.summary = %q{Rails Form Object - additional layer between form and model}
13
+ spec.homepage = "http://github.com/petergebala/rfo"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake', '~> 0'
23
+ spec.add_runtime_dependency 'virtus', '~> 1.0'
24
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rfo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Gębala
8
+ - Bartłomiej Oleszczyk
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-07-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '1.3'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '1.3'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: virtus
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '1.0'
56
+ description: Rails Form Object
57
+ email:
58
+ - piotrek.gebala@gmail.com
59
+ - bart@primate.co.uk
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - .gitignore
65
+ - Gemfile
66
+ - LICENSE
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - lib/rfo.rb
71
+ - lib/rfo/base.rb
72
+ - lib/rfo/version.rb
73
+ - rfo.gemspec
74
+ homepage: http://github.com/petergebala/rfo
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.2.1
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Rails Form Object - additional layer between form and model
98
+ test_files: []
99
+ has_rdoc: