reform 1.2.0.beta2 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d54671e9b119dc5bbe6b8b405c0f2f947a3004b
4
- data.tar.gz: bc1ed19c08fd3f91435cc4b20e512fc9c34262c5
3
+ metadata.gz: d8d0dc76658a132e15bea8612f35e7bfa77fb7b8
4
+ data.tar.gz: 31226f61d63c42b0a641b5592eb9714cb29bcff2
5
5
  SHA512:
6
- metadata.gz: 7a53b1ec82dca50a28734ad805a0555e30e2c9d403d0e9ca51765bceffe19cf32f325f20ed7b2356853463053aff56d1953b1eca027a3936cd877840a248f63d
7
- data.tar.gz: ca494adb0607285f727a3562609526c06ad9f4fb0ff25143395731db5d9ad17dec892ba14bf7dadc58f27ab5deaee468947bd61c679ce80a32b43cb90d97ddbd
6
+ metadata.gz: cde409c7fa9dc9054d05e2dafb7cf672abf8585d85b97f05f8f0d37b0d6c0de25e554775fb7a8a5eec57567977ba8ff03cdcb2276190c964e5f62c31259ce027
7
+ data.tar.gz: 70ac7b9421a13e0936ba9e149f611ad9f540b071f1f557d474a191c9c1d4a7e2a8fc126a9510a7fed65b1ef70078033ea4e8f34a6d9d2bc3dd9b74fbc61bd1c3
data/.travis.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.1.4
3
4
  - 2.0.0
4
5
  - 1.9.3
5
6
  gemfile:
6
7
  - gemfiles/Gemfile.rails-4.0
7
8
  - gemfiles/Gemfile.rails-3.2
8
9
  - gemfiles/Gemfile.rails-3.1
9
- - gemfiles/Gemfile.rails-3.0
10
+ - gemfiles/Gemfile.rails-3.0
data/CHANGES.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 1.2.1
2
+
3
+ * Fixed a nasty bug where `ActiveModel` forms with form builder support wouldn't deserialize properly. A million Thanks to @karolsarnacki for finding this and providing an exemplary failing test. <3
4
+
1
5
  ## 1.2.0
2
6
 
3
7
  ### Breakage
@@ -11,16 +15,29 @@
11
15
 
12
16
  Including this module will add `#column_for_attribute` and other methods need by form builders to automatically guess the type of a property.
13
17
 
18
+ * `Form#save` no longer passed `self` to the block. You've been warned long enough. ;)
19
+
14
20
  ### Changes
15
21
 
22
+ * Renamed `:as` to `:from` to be in line with Representable/Roar, Disposable and Cells. Thanks to @bethesque for pushing this.
23
+ * `:empty` is now `:virtual` and `:virtual` is `writeable: false`. It was too confusing and sucked. Thanks to @bethesque, again, for her moral assistance.
16
24
  * `Form#save` with `Composition` now returns true only if all composite models saved.
17
25
  * `Form::copy_validations_from` allows copying custom validators now.
18
26
  * New call style for `::properties`. Instead of an array, it's now `properties :title, :genre`.
19
27
  * All options are evaluated with `pass_options: true`.
28
+ * All transforming representers are now created and stored on class level, resulting in simpler code and a 85% speed-up.
20
29
 
21
30
  ### New Stuff!!!
22
31
 
23
- * `:skip_if`, `:skip_if: :all_blank`.
32
+ * In `#validate`, you can ignore properties now using `:skip_if`.
33
+
34
+ ```ruby
35
+ property :hit, skip_if: lambda { |fragment, *| fragment["title"].blank? }
36
+ ```
37
+
38
+ This works for both properties and nested forms. The property will simply be ignored when deserializing, as if it had never been in the incoming hash/document.
39
+
40
+ For nested properties you can use `:skip_if: :all_blank` as a macro to ignore a nested form if all values are blank.
24
41
  * You can now specify validations right in the `::property` call.
25
42
 
26
43
  ```ruby
@@ -28,19 +45,48 @@
28
45
  ```
29
46
 
30
47
  Thanks to @zubin for this brillant idea!
31
- * Wanna parse JSON in `#validate`? Include `Reform::Form::JSON`.
32
- * Dirty
33
- * :sync
34
- * :save
35
- * `Sync::SkipUnchanged`.
36
- * Adding `:base` errors now works. Thanks to @bethesque.
48
+
49
+ * Reform now tracks which attributes have changed after `#validate`. You can check that using `form.changed?(:title)`.
50
+ * When including `Sync::SkipUnchanged`, the form won't try to assign unchanged values anymore in `#sync`. This is extremely helpful when handling file uploads and the like.
51
+ * Both `#sync` and `#save` can be configured dynamically now.
52
+
53
+ When syncing, you can run a lambda per property.
54
+
55
+ ```ruby
56
+ property :title, sync: lambda { |value, options| model.set_title(value) }
57
+ ```
58
+
59
+ This won't run Reform's built-in sync for this property.
60
+
61
+ You can also provide the sync lambda at run-time.
62
+
63
+ ```ruby
64
+ form.sync(title: lambda { |value, options| form.model.title = "HOT: #{value}" })
65
+ ```
66
+
67
+ This block is run in the caller's context allowing you to access environment variables.
68
+
69
+ Note that the dynamic sync happens _before_ save, so the model id may unavailable.
70
+
71
+ You can do the same for saving.
72
+
73
+ ```ruby
74
+ form.save(title: lambda { |value, options| form.model.title = "#{form.model.id} --> #{value}" })
75
+ ```
76
+ Again, this block is run in the caller's context.
77
+
78
+ The two features are an excellent way to handle file uploads without ActiveRecord's horrible callbacks.
79
+
80
+ * Adding generic `:base` errors now works. Thanks to @bethesque.
37
81
 
38
82
  ```ruby
39
83
  errors.add(:base, "You are too awesome!")
40
84
  ```
41
85
 
42
86
  This will prefix the error with `:base`.
87
+ * Need your form to parse JSON? Include `Reform::Form::JSON`, the `#validate` method now expects a JSON string and will deserialize and populate the form from the JSON document.
43
88
  * Added `Form::schema` to generate a pure representer from the form's representer.
89
+ * Added `:readable` and `:writeable` option which allow to skip reading or writing to the model when `false`.
44
90
 
45
91
  ## 1.1.1
46
92
 
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Nick Sutterer
1
+ Copyright (c) 2013 - 2014 Nick Sutterer
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -98,11 +98,11 @@ class SongForm < Reform::Form
98
98
 
99
99
  Internally, this form will call `song.title` to populate the title field.
100
100
 
101
- If you, for whatever reasons, want to use a different public name, use `:as`.
101
+ If you, for whatever reasons, want to use a different public name, use `:from`.
102
102
 
103
103
  ```ruby
104
104
  class SongForm < Reform::Form
105
- property :name, as: :title
105
+ property :name, from: :title
106
106
  ```
107
107
 
108
108
  This will still call `song.title` but expose the attribute as `name`.
@@ -633,17 +633,17 @@ As with the built-in coercion, this setter is only called in `#validate`.
633
633
  Virtual fields come in handy when there's no direct mapping to a model attribute or when you plan on displaying but not processing a value.
634
634
 
635
635
 
636
- ### Empty Fields
636
+ ### Virtual Fields
637
637
 
638
- Often, fields like `password_confirmation` shouldn't be retrieved from the model. Reform comes with the `:empty` option for that.
638
+ Often, fields like `password_confirmation` should neither be read from nor written back to the model. Reform comes with the `:virtual` option to handle that case.
639
639
 
640
640
  ```ruby
641
641
  class PasswordForm < Reform::Form
642
642
  property :password
643
- property :password_confirmation, :empty => true
643
+ property :password_confirmation, virtual: true
644
644
  ```
645
645
 
646
- Here, the model won't be queried for a `password_confirmation` field when creating and rendering the form. Likewise, when saving the form, the input value is not written to the decorated model. It is only readable in validations and when saving the form.
646
+ Here, the model won't be queried for a `password_confirmation` field when creating and rendering the form. When saving the form, the input value is not written to the decorated model. It is only readable in validations and when saving the form manually.
647
647
 
648
648
  ```ruby
649
649
  form.validate("password" => "123", "password_confirmation" => "321")
@@ -660,11 +660,11 @@ form.save do |nested|
660
660
 
661
661
  ### Read-Only Fields
662
662
 
663
- Almost identical, the `:virtual` option makes fields read-only. Say you want to show a value, but not process it after submission, this option is your friend.
663
+ When you want to show a value but skip processing it after submission the `:writeable` option is your friend.
664
664
 
665
665
  ```ruby
666
666
  class ProfileForm < Reform::Form
667
- property :country, :virtual => true
667
+ property :country, :writeable => false
668
668
  ```
669
669
 
670
670
  This time reform will query the model for the value by calling `model.country`.
@@ -678,6 +678,14 @@ form.save do |nested|
678
678
  nested[:country] #=> "Australia"
679
679
  ```
680
680
 
681
+ ### Write-Only Fields
682
+
683
+ A third alternative is to hide a field's value but write it to the database when syncing. This can be achieved using the `:readable` option.
684
+
685
+ ```ruby
686
+ property :credit_card_number, :readable => false
687
+ ```
688
+
681
689
  ## Validations From Models
682
690
 
683
691
  Sometimes when you still keep validations in your models (which you shouldn't) copying them to a form might not feel right. In that case, you can let Reform automatically copy them.
@@ -869,6 +877,38 @@ form.validate("title" => "Just Kiddin'")
869
877
  form.changed?(:title) #=> true
870
878
  ```
871
879
 
880
+ When including `Sync::SkipUnchanged`, the form won't assign unchanged values anymore in `#sync`.
881
+
882
+
883
+ ## Dynamically Syncing And Saving Properties
884
+
885
+ Both `#sync` and `#save` can be configured to run a dynamical lambda per property.
886
+
887
+ The `sync:` option allows to statically add a lambda to a property.
888
+
889
+ ```ruby
890
+ property :title, sync: lambda { |value, options| model.set_title(value) }
891
+ ```
892
+
893
+ Instead of running Reform's built-in sync for this property the block is run.
894
+
895
+ You can also provide the sync lambda at run-time.
896
+
897
+ ```ruby
898
+ form.sync(title: lambda { |value, options| form.model.title = "HOT: #{value}" })
899
+ ```
900
+
901
+ This block is run in the caller's context allowing you to access environment variables. Note that the dynamic sync happens _before_ save, so the model id may unavailable.
902
+
903
+ You can do the same for saving.
904
+
905
+ ```ruby
906
+ form.save(title: lambda { |value, options| form.model.title = "#{form.model.id} --> #{value}" })
907
+ ```
908
+ Again, this block is run in the caller's context.
909
+
910
+ The two features are an excellent way to handle file uploads without ActiveRecord's horrible callbacks.
911
+
872
912
 
873
913
  ## Undocumented Features
874
914
 
data/database.sqlite3 CHANGED
Binary file
@@ -29,12 +29,27 @@ module Reform
29
29
 
30
30
  module PropertyMethods
31
31
  def property(name, options={}, &block)
32
- options[:private_name] = options.delete(:as)
32
+ deprecate_as!(options)
33
+ options[:private_name] = options.delete(:from)
33
34
  options[:coercion_type] = options.delete(:type)
34
35
  options[:features] ||= []
35
36
  options[:features] += features.keys if block_given?
36
37
  options[:pass_options] = true
37
- options[:virtual] = true if options[:empty] # TODO: check TODO and fix naming!
38
+
39
+ # readable and writeable is true as it's not == false
40
+
41
+ if reform_2_0
42
+ if options.delete(:virtual)
43
+ options[:_readable] = false
44
+ options[:_writeable] = false
45
+ else
46
+ options[:_readable] = options.delete(:readable)
47
+ options[:_writeable] = options.delete(:writeable)
48
+ end
49
+
50
+ else # TODO: remove me in 2.0.
51
+ deprecate_virtual_and_empty!(options)
52
+ end
38
53
 
39
54
  validates(name, options.delete(:validates).dup) if options[:validates]
40
55
 
@@ -98,10 +113,24 @@ module Reform
98
113
 
99
114
  require 'reform/contract/setup'
100
115
  include Setup
116
+
117
+ def self.representers # keeps all transformation representers for one class.
118
+ @representers ||= {}
119
+ end
120
+
121
+ def self.representer(name=nil, options={}, &block)
122
+ return representer_class.each(&block) if name == nil
123
+ return representers[name] if representers[name] # don't run block as this representer is already setup for this form class.
124
+
125
+ only_forms = options[:all] ? false : true
126
+ base = options[:superclass] || representer_class
127
+
128
+ representers[name] = Class.new(base).each(only_forms, &block) # let user modify representer.
129
+ end
130
+
101
131
  require 'reform/contract/validate'
102
132
  include Validate
103
133
 
104
-
105
134
  def errors # FIXME: this is needed for Rails 3.0 compatibility.
106
135
  @errors ||= Errors.new(self)
107
136
  end
@@ -111,10 +140,35 @@ module Reform
111
140
  attr_accessor :fields
112
141
  attr_writer :errors # only used in top form. (is that true?)
113
142
 
114
- def mapper
143
+ def mapper # FIXME: do we need this with class-level representers?
115
144
  self.class.representer_class
116
145
  end
117
146
 
147
+ def self.deprecate_as!(options) # TODO: remove me in 2.0.
148
+ return unless as = options.delete(:as)
149
+ options[:from] = as
150
+ warn "[Reform] The :as options got renamed to :from. See https://github.com/apotonick/reform/wiki/Migration-Guide and have a nice day."
151
+ end
152
+
153
+ def self.deprecate_virtual_and_empty!(options) # TODO: remove me in 2.0.
154
+ if options.delete(:virtual)
155
+ warn "[Reform] The :virtual option has changed! Check https://github.com/apotonick/reform/wiki/Migration-Guide and have a good day."
156
+ options[:_readable] = true
157
+ options[:_writeable] = false
158
+ end
159
+
160
+ if options[:empty]
161
+ warn "[Reform] The :empty option has changed! Check https://github.com/apotonick/reform/wiki/Migration-Guide and have a good day."
162
+ options[:_readable] = false
163
+ options[:_writeable] = false
164
+ end
165
+ end
166
+
167
+ inheritable_attr :reform_2_0 # TODO: remove me in 2.0.
168
+ def self.reform_2_0!
169
+ self.reform_2_0= true
170
+ end
171
+
118
172
  def self.register_feature(mod)
119
173
  features[mod] = true
120
174
  end
@@ -6,14 +6,25 @@ module Reform
6
6
  @fields = setup_fields # delegate all methods to Fields instance.
7
7
  end
8
8
 
9
+ # Setup#to_hash will create a nested hash of property values from the model.
10
+ # Nested properties will be recursively wrapped in a form instance.
11
+ def setup_representer
12
+ self.class.representer(:setup) do |dfn| # only nested forms.
13
+ dfn.merge!(
14
+ :representable => false, # don't call #to_hash, only prepare.
15
+ :prepare => lambda { |model, args| args.binding[:form].new(model) } # wrap nested properties in form.
16
+ )
17
+ end
18
+ end
19
+
9
20
  def setup_fields
10
- representer = mapper.new(aliased_model).extend(Setup::Representer)
21
+ representer = setup_representer.new(aliased_model)
11
22
  options = setup_options(Reform::Representer::Options[]) # handles :empty.
12
23
 
24
+ # populate the internal @fields set with data from the model.
13
25
  create_fields(mapper.fields, representer.to_hash(options))
14
26
  end
15
27
 
16
- # DISCUSS: setting up the Validation (populating with values) will soon be handled with Disposable::Twin logic.
17
28
  def create_fields(field_names, fields)
18
29
  Fields.new(field_names, fields)
19
30
  end
@@ -26,31 +37,14 @@ module Reform
26
37
  include SetupOptions
27
38
 
28
39
 
29
- # Mechanics for setting up initial Field values.
30
- module Representer
31
- def to_hash(*)
32
- nested_forms do |attr|
33
- attr.merge!(
34
- :representable => false, # don't call #to_hash.
35
- :prepare => lambda do |model, args|
36
- args.binding[:form].new(model)
37
- end
38
- )
39
- end
40
-
41
- super
42
- end
43
- end # Representer
44
-
45
-
46
- module Empty
40
+ module Readable
47
41
  def setup_options(options)
48
- empty_fields = mapper.representable_attrs.find_all { |d| d[:empty] }.collect { |d| d.name.to_sym }
42
+ empty_fields = mapper.representable_attrs.find_all { |d| d[:_readable] == false }.collect { |d| d.name.to_sym }
49
43
 
50
44
  options.exclude!(empty_fields)
51
45
  end
52
46
  end
53
- include Empty
47
+ include Readable
54
48
  end
55
49
  end # Setup
56
50
  end
@@ -1,24 +1,4 @@
1
1
  module Reform::Contract::Validate
2
- module NestedValid
3
- def to_hash(*)
4
- nested_forms do |attr|
5
- attr.merge!(
6
- :serialize => lambda { |object, args|
7
-
8
- # FIXME: merge with Validate::Writer
9
- options = args.user_options.dup
10
- options[:prefix] = options[:prefix].dup # TODO: implement Options#dup.
11
- options[:prefix] << args.binding.name # FIXME: should be #as.
12
-
13
- object.validate!(options) # recursively call valid?
14
- },
15
- )
16
- end
17
-
18
- super
19
- end
20
- end
21
-
22
2
  def validate
23
3
  options = {:errors => errs = Reform::Contract::Errors.new(self), :prefix => []}
24
4
 
@@ -28,15 +8,32 @@ module Reform::Contract::Validate
28
8
 
29
9
  errors.valid?
30
10
  end
11
+
31
12
  def validate!(options)
32
13
  prefix = options[:prefix]
33
14
 
34
15
  # call valid? recursively and collect nested errors.
35
- mapper.new(fields).extend(NestedValid).to_hash(options) # TODO: only include nested forms here.
16
+ valid_representer.new(fields).to_hash(options) # TODO: only include nested forms here.
36
17
 
37
18
  valid? # this validates on <Fields> using AM::Validations, currently.
38
19
 
39
20
  options[:errors].merge!(self.errors, prefix)
40
21
  end
41
22
 
23
+ private
24
+
25
+ # runs form.validate! on all nested forms
26
+ def valid_representer
27
+ self.class.representer(:valid) do |dfn|
28
+ dfn.merge!(
29
+ :serialize => lambda { |form, args|
30
+ options = args.user_options.dup
31
+ options[:prefix] = options[:prefix].dup # TODO: implement Options#dup.
32
+ options[:prefix] << args.binding.name # FIXME: should be #as.
33
+
34
+ form.validate!(options) # recursively call valid? on nested form.
35
+ }
36
+ )
37
+ end
38
+ end
42
39
  end
@@ -29,22 +29,20 @@ module Reform::Form::ActiveModel
29
29
  return super unless params.is_a?(Hash)
30
30
  # TODO: run this only for hash deserialization, but generically (#deserialize_hash ?).
31
31
 
32
- mapper.new(self).nested_forms do |attr, model| # FIXME: make this simpler.
33
- rename_nested_param_for!(params, attr)
34
- end
32
+ self.class.representer { |dfn| rename_nested_param_for!(params, dfn) }
35
33
 
36
34
  super
37
35
  end
38
36
 
39
37
  private
40
- def rename_nested_param_for!(params, attr)
41
- nested_name = "#{attr.name}_attributes"
38
+ def rename_nested_param_for!(params, dfn)
39
+ nested_name = "#{dfn.name}_attributes"
42
40
  return unless params.has_key?(nested_name)
43
41
 
44
- value = params["#{attr.name}_attributes"]
45
- value = value.values if attr[:collection]
42
+ value = params["#{dfn.name}_attributes"]
43
+ value = value.values if dfn[:collection]
46
44
 
47
- params[attr.name] = value
45
+ params[dfn.name] = value
48
46
  end
49
47
  end # FormBuilderMethods
50
48