reform 0.2.7 → 1.0.0

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.
@@ -3,13 +3,15 @@ module Reform::Form::ActiveModel
3
3
  def self.included(base)
4
4
  base.class_eval do
5
5
  extend ClassMethods # ::model_name
6
+ features << FormBuilderMethods
6
7
  end
7
8
  end
8
9
 
9
10
  module ClassMethods
10
11
  def property(name, options={})
11
- super
12
- add_nested_attribute_compat(name) if options[:form] # TODO: fix that in Rails FB#1832 work.
12
+ super.tap do |definition|
13
+ add_nested_attribute_compat(name) if definition[:form] # TODO: fix that in Rails FB#1832 work.
14
+ end
13
15
  end
14
16
 
15
17
  private
@@ -20,7 +22,7 @@ module Reform::Form::ActiveModel
20
22
  end
21
23
 
22
24
  # Modify the incoming Rails params hash to be representable compliant.
23
- def validate(params)
25
+ def update!(params)
24
26
  # DISCUSS: #validate should actually expect the complete params hash and then pick the right key as it knows the form name.
25
27
  # however, this would cause confusion?
26
28
  mapper.new(self).nested_forms do |attr, model| # FIXME: make this simpler.
@@ -36,16 +38,17 @@ module Reform::Form::ActiveModel
36
38
  return unless params.has_key?(nested_name)
37
39
 
38
40
  value = params["#{attr.name}_attributes"]
39
- value = value.values if attr.options[:form_collection]
41
+ value = value.values if attr[:collection]
40
42
 
41
43
  params[attr.name] = value
42
44
  end
43
- end
45
+ end # FormBuilderMethods
44
46
 
45
47
 
46
48
  def self.included(base)
47
49
  base.class_eval do
48
50
  extend ClassMethods
51
+ features << ActiveModel
49
52
 
50
53
  delegate [:persisted?, :to_key, :to_param, :id] => :model
51
54
 
@@ -1,50 +1,43 @@
1
- class Reform::Form
2
- module ActiveRecord
3
- def self.included(base)
4
- base.class_eval do
5
- include Reform::Form::ActiveModel
6
- extend ClassMethods
7
- end
1
+ module Reform::Form::ActiveRecord
2
+ def self.included(base)
3
+ base.class_eval do
4
+ include Reform::Form::ActiveModel
5
+ extend ClassMethods
8
6
  end
7
+ end
9
8
 
10
- module ClassMethods
11
- def validates_uniqueness_of(attribute)
12
- validates_with UniquenessValidator, :attributes => [attribute]
13
- end
14
- def i18n_scope
15
- :activerecord
16
- end
9
+ module ClassMethods
10
+ def validates_uniqueness_of(attribute)
11
+ validates_with UniquenessValidator, :attributes => [attribute]
12
+ end
13
+ def i18n_scope
14
+ :activerecord
17
15
  end
16
+ end
18
17
 
19
- class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
20
- # when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
21
- def validate(form)
22
- property = attributes.first
18
+ class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
19
+ # when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
20
+ def validate(form)
21
+ property = attributes.first
23
22
 
24
- # here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
25
- # the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
26
- # the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
27
- record = form.model_for_property(property)
28
- record.send("#{property}=", form.send(property))
23
+ # here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
24
+ # the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
25
+ # the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
26
+ record = form.model_for_property(property)
27
+ record.send("#{property}=", form.send(property))
29
28
 
30
- @klass = record.class # this is usually done in the super-sucky #setup method.
31
- super(record).tap do |res|
32
- form.errors.add(property, record.errors.first.last) if record.errors.present?
33
- end
29
+ @klass = record.class # this is usually done in the super-sucky #setup method.
30
+ super(record).tap do |res|
31
+ form.errors.add(property, record.errors.first.last) if record.errors.present?
34
32
  end
35
33
  end
34
+ end
36
35
 
37
- def save(*)
38
- super.tap do
39
- model.save unless block_given? # DISCUSS: should we implement nested saving here?
40
- end
41
- end
42
36
 
43
- def model_for_property(name)
44
- return model unless is_a?(Reform::Form::Composition) # i am too lazy for proper inheritance. there should be a ActiveRecord::Composition that handles this.
37
+ def model_for_property(name)
38
+ return model unless is_a?(Reform::Form::Composition) # i am too lazy for proper inheritance. there should be a ActiveRecord::Composition that handles this.
45
39
 
46
- model_name = mapper.representable_attrs[name].options[:on]
47
- send(model_name)
48
- end
40
+ model_name = mapper.representable_attrs[name][:on]
41
+ send(model_name)
49
42
  end
50
43
  end
@@ -1,17 +1,16 @@
1
1
  require 'representable/decorator/coercion'
2
2
 
3
- class Reform::Form
4
- module Coercion
5
- def self.included(base)
6
- base.extend(ClassMethods)
7
- end
3
+ module Reform::Form::Coercion
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ base.features << self
7
+ end
8
8
 
9
- module ClassMethods
10
- def representer_class
11
- super.class_eval do
12
- include Representable::Decorator::Coercion unless self < Representable::Decorator::Coercion # DISCUSS: include it once. why do we have to check this?
13
- self
14
- end
9
+ module ClassMethods
10
+ def representer_class
11
+ super.class_eval do
12
+ include Representable::Decorator::Coercion unless self < Representable::Decorator::Coercion # DISCUSS: include it once. why do we have to check this?
13
+ self
15
14
  end
16
15
  end
17
16
  end
@@ -1,70 +1,60 @@
1
1
  require "reform/form/active_model"
2
2
 
3
- class Reform::Form
3
+ module Reform::Form::Composition
4
4
  # Automatically creates a Composition object for you when initializing the form.
5
- module Composition
6
- def self.included(base)
7
- base.class_eval do
8
- extend Reform::Form::ActiveModel::ClassMethods # ::model.
9
- extend ClassMethods
10
- end
5
+ def self.included(base)
6
+ base.class_eval do
7
+ extend Reform::Form::ActiveModel::ClassMethods # ::model.
8
+ extend ClassMethods
11
9
  end
10
+ end
12
11
 
13
- module ClassMethods
14
- #include Reform::Form::ActiveModel::ClassMethods # ::model.
12
+ module ClassMethods
13
+ #include Reform::Form::ActiveModel::ClassMethods # ::model.
15
14
 
16
- def model_class # DISCUSS: needed?
17
- rpr = representer_class
18
- @model_class ||= Class.new(Reform::Composition) do
19
- map_from rpr
20
- end
21
- end
15
+ def model_class # DISCUSS: needed?
16
+ @model_class ||= Reform::Composition.from(representer_class)
17
+ end
22
18
 
23
- def property(name, options={})
24
- super
25
- delegate options[:on] => :@model
19
+ def property(name, options={})
20
+ super.tap do |definition|
21
+ delegate options[:on] => :@model # form.band -> composition.band
26
22
  end
23
+ end
27
24
 
28
- # Same as ActiveModel::model but allows you to define the main model in the composition
29
- # using +:on+.
30
- #
31
- # class CoverSongForm < Reform::Form
32
- # model :song, on: :cover_song
33
- def model(main_model, options={})
34
- super
35
-
36
- composition_model = options[:on] || main_model
25
+ # Same as ActiveModel::model but allows you to define the main model in the composition
26
+ # using +:on+.
27
+ #
28
+ # class CoverSongForm < Reform::Form
29
+ # model :song, on: :cover_song
30
+ def model(main_model, options={})
31
+ super
37
32
 
38
- delegate composition_model => :model # #song => model.song
33
+ composition_model = options[:on] || main_model
39
34
 
40
- # FIXME: this should just delegate to :model as in FB, and the comp would take care of it internally.
41
- delegate [:persisted?, :to_key, :to_param] => composition_model # #to_key => song.to_key
35
+ delegate composition_model => :model # #song => model.song
42
36
 
43
- alias_method main_model, composition_model # #hit => model.song.
44
- end
45
- end
37
+ # FIXME: this should just delegate to :model as in FB, and the comp would take care of it internally.
38
+ delegate [:persisted?, :to_key, :to_param] => composition_model # #to_key => song.to_key
46
39
 
47
- def initialize(models)
48
- composition = self.class.model_class.new(models)
49
- super(composition)
50
- end
51
-
52
- def to_nested_hash
53
- model.nested_hash_for(to_hash) # use composition to compute nested hash.
40
+ alias_method main_model, composition_model # #hit => model.song.
54
41
  end
55
42
  end
56
43
 
44
+ def initialize(models)
45
+ composition = self.class.model_class.new(models)
46
+ super(composition)
47
+ end
57
48
 
58
- # TODO: remove me in 1.3.
59
- module DSL
60
- include Composition
49
+ def aliased_model # we don't need an Expose as we save the Composition instance in the constructor.
50
+ model
51
+ end
61
52
 
62
- def self.included(base)
63
- warn "[DEPRECATION] Reform::Form: `DSL` is deprecated. Please use `Composition` instead."
53
+ def to_nested_hash
54
+ model.nested_hash_for(to_hash) # use composition to compute nested hash.
55
+ end
64
56
 
65
- base.class_eval do
66
- extend Composition::ClassMethods
67
- end
68
- end
57
+ def to_hash(*args)
58
+ mapper.new(self).to_hash(*args)
69
59
  end
70
- end
60
+ end
@@ -1,5 +1,9 @@
1
- class Reform::Form
1
+ Reform::Form.class_eval do
2
2
  module MultiParameterAttributes
3
+ def self.included(base)
4
+ base.features << self
5
+ end
6
+
3
7
  class DateParamsFilter
4
8
  def call(params)
5
9
  date_attributes = {}
@@ -33,6 +37,7 @@ class Reform::Form
33
37
 
34
38
  def validate(params)
35
39
  # TODO: make it cleaner to hook into essential reform steps.
40
+ # TODO: test with nested.
36
41
  DateParamsFilter.new.call(params)
37
42
 
38
43
  super
@@ -0,0 +1,61 @@
1
+ Reform::Form.class_eval do
2
+ module Save
3
+ module RecursiveSave
4
+ def to_hash(*)
5
+ # process output from InputRepresenter {title: "Mint Car", hit: <Form>}
6
+ # and just call sync! on nested forms.
7
+ nested_forms do |attr|
8
+ attr.merge!(
9
+ :instance => lambda { |fragment, *| fragment },
10
+ :serialize => lambda { |object, args| object.save! unless args.binding[:save] === false },
11
+ )
12
+ end
13
+
14
+ super
15
+ end
16
+ end
17
+
18
+ def save
19
+ # DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
20
+ return yield self, to_nested_hash if block_given?
21
+
22
+ sync_models # recursion
23
+ save!
24
+ end
25
+
26
+ def save!
27
+ save_model
28
+ mapper.new(self).extend(RecursiveSave).to_hash # save! on all nested forms.
29
+ end
30
+
31
+ def save_model
32
+ model.save # TODO: implement nested (that should really be done by Twin/AR).
33
+ end
34
+
35
+
36
+ module NestedHash
37
+ def to_hash(*)
38
+ # Transform form data into a nested hash for #save.
39
+ nested_forms do |attr|
40
+ attr.merge!(
41
+ :instance => lambda { |fragment, *| fragment },
42
+ :serialize => lambda { |object, args| object.to_nested_hash },
43
+ )
44
+ end
45
+
46
+ representable_attrs.each do |attr|
47
+ attr.merge!(:as => attr[:private_name] || attr.name)
48
+ end
49
+
50
+ super
51
+ end
52
+ end
53
+
54
+ require "active_support/hash_with_indifferent_access" # DISCUSS: replace?
55
+ def to_nested_hash
56
+ map = mapper.new(self).extend(Save::NestedHash)
57
+
58
+ ActiveSupport::HashWithIndifferentAccess.new(map.to_hash)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,60 @@
1
+ Reform::Form.class_eval do
2
+ # #sync!
3
+ # 1. assign scalars to model (respecting virtual, excluded attributes)
4
+ # 2. call sync! on nested
5
+ module Sync
6
+ # Mechanics for writing input to model.
7
+ # Writes input to model.
8
+ module Writer
9
+ def from_hash(*)
10
+ # process output from InputRepresenter {title: "Mint Car", hit: <Form>}
11
+ # and just call sync! on nested forms.
12
+ nested_forms do |attr|
13
+ attr.merge!(
14
+ :instance => lambda { |fragment, *| fragment },
15
+ :deserialize => lambda { |object, *| object.sync! },
16
+ :setter => lambda { |*| } # don't write hit=<Form>.
17
+ )
18
+ end
19
+
20
+ super
21
+ end
22
+ end
23
+
24
+ # Transforms form input into what actually gets written to model.
25
+ # output: {title: "Mint Car", hit: <Form>}
26
+ module InputRepresenter
27
+ include Reform::Representer::WithOptions
28
+ # TODO: make dynamic.
29
+ include Reform::Form::EmptyAttributesOptions
30
+ include Reform::Form::ReadonlyAttributesOptions
31
+
32
+ def to_hash(*)
33
+ nested_forms do |attr|
34
+ attr.merge!(
35
+ :representable => false,
36
+ :prepare => lambda { |obj, *| obj }
37
+ )
38
+
39
+ end
40
+
41
+ super
42
+ end
43
+ end
44
+ end
45
+
46
+ ### TODO: add ToHash with :prepare => lambda { |form, args| form },
47
+
48
+ def sync_models
49
+ sync!
50
+ end
51
+ alias_method :sync, :sync_models
52
+
53
+ def sync! # semi-public.
54
+ input_representer = mapper.new(self).extend(Sync::InputRepresenter)
55
+
56
+ input = input_representer.to_hash
57
+
58
+ mapper.new(aliased_model).extend(Sync::Writer).from_hash(input)
59
+ end
60
+ end
@@ -0,0 +1,104 @@
1
+ # Mechanics for writing to forms in #validate.
2
+ module Reform::Form::Validate
3
+ module Update
4
+ def from_hash(*)
5
+ nested_forms do |attr|
6
+ attr.delete(:prepare)
7
+ attr.delete(:extend)
8
+
9
+ attr.merge!(
10
+ :collection => attr[:collection], # TODO: Def#merge! doesn't consider :collection if it's already set in attr YET.
11
+ :parse_strategy => :sync, # just use nested objects as they are.
12
+ :deserialize => lambda { |object, params, args| object.update!(params) },
13
+ )
14
+ end
15
+
16
+ super
17
+ end
18
+ end
19
+
20
+
21
+ module Populator
22
+ class PopulateIfEmpty
23
+ def initialize(*args)
24
+ @form, @fragment, args = args
25
+ @index = args.first
26
+ @args = args.last
27
+ end
28
+
29
+ def call
30
+ binding = @args.binding
31
+ form = binding.get
32
+
33
+ return if binding.array? and form and form[@index] # TODO: this should be handled by the Binding.
34
+ return if !binding.array? and form
35
+ # only get here when above form is nil.
36
+
37
+ if binding[:populate_if_empty].is_a?(Proc)
38
+ model = @form.instance_exec(@fragment, @args, &binding[:populate_if_empty]) # call user block.
39
+ else
40
+ model = binding[:populate_if_empty].new
41
+ end
42
+
43
+ form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup.
44
+
45
+ if binding.array?
46
+ @form.model.send("#{binding.getter}") << model # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. i have to use #<< to stay compatible with AR's has_many API. DISCUSS: what happens when we get out-of-sync here?
47
+ @form.send("#{binding.getter}")[@index] = form
48
+ else
49
+ @form.model.send("#{binding.setter}", model) # FIXME: i don't like this, but we have to add the model to the parent object to make associating work.
50
+ @form.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy.
51
+ end
52
+ end
53
+ end # PopulateIfEmpty
54
+
55
+
56
+ def from_hash(params, *args)
57
+ populated_attrs = []
58
+
59
+ nested_forms do |attr|
60
+ next unless attr[:populate_if_empty]
61
+
62
+ attr.merge!(
63
+ # DISCUSS: it would be cool to move the lambda block to PopulateIfEmpty#call.
64
+ :populator => lambda do |fragment, *args|
65
+ PopulateIfEmpty.new(self, fragment, args).call
66
+ end
67
+ )
68
+ end
69
+
70
+
71
+ nested_forms do |attr|
72
+ next unless attr[:populator]
73
+
74
+ attr.merge!(
75
+ :parse_strategy => attr[:populator],
76
+ :representable => false
77
+ )
78
+ populated_attrs << attr.name.to_sym
79
+ end
80
+
81
+ super(params, {:include => populated_attrs})
82
+ end
83
+ end
84
+
85
+
86
+ def validate(params)
87
+ update!(params)
88
+
89
+ super()
90
+ end
91
+
92
+ def update!(params)
93
+ # puts "updating in #{self.class.name}"
94
+ populate!(params)
95
+
96
+ mapper.new(self).extend(Update).from_hash(params)
97
+ end
98
+
99
+ private
100
+ def populate!(params)
101
+ mapper.new(self).extend(Populator).from_hash(params)
102
+ end
103
+
104
+ end