act_form 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 50804b31cfd1b52553e16af190b4f8471cdd6e2f
4
- data.tar.gz: 6392da038a5fdf1ef6f9e70376b5a0efdf791d30
3
+ metadata.gz: 439172cc129e1aaa79e835d567a359f643010de0
4
+ data.tar.gz: af5f2aa3c281e2f5a6b6d2f970700a9105aecd85
5
5
  SHA512:
6
- metadata.gz: 7cbf1b8b3a10db0f6d9569953593267a1de6935ed7a40ea20e6a902aac71857559f0c5d7cd1f2dfc5bd7dc53166d342589de5006996d1bc605fe9fc10346e1dc
7
- data.tar.gz: 251cef49ed01f2133b23cbac6c81b07c45807aee3853e1d1295901723d0603f7209fb78bece6923e755c88382bad6fb03a587db6a909a87bed289a0156da5900
6
+ metadata.gz: 5e069064c6100585124fa64e66594bbd0a055aaac0b31a809dd90d04ec1450cfb78c0627c0b55f96fc71573fa81f0a225bb721046a968aee56854e6fd63f47dd
7
+ data.tar.gz: 18b92c9422e7c4585ada6002e5fe4ab0e9e3e9733864caebc7f02780abf3cea6881382bcb0980626a0d560a157be677a9bad139595b4037c95d73f3a47dc992a
data/README.md CHANGED
@@ -1,9 +1,170 @@
1
1
  # ActForm
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/form_model`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ActForm is the gem that provide a simple way to create `form object` or `command object` or `service object`, it only depends on `activemodel >= 5` and provides few api.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ## Usage
6
+
7
+ #### API - `attribute`
8
+
9
+ ```ruby
10
+ class UserForm < ActForm::Base
11
+ attribute :name, required: true
12
+ attribute :age, type: :integer
13
+ attribute :address
14
+ attribute :nickname, default: 'nick'
15
+ attribute :desc, default: ->{ 'desc' }
16
+ end
17
+
18
+ form = UserForm.new(name: 'su', age: '18', address: 'somewhere')
19
+ form.name # => 'su'
20
+ form.age # => 18
21
+ form.address # => 'somewhere'
22
+ form.nickname # => 'nick'
23
+ form.desc # => 'desc'
24
+ # override default
25
+ form.nickname = 'hello'
26
+ form.nickname # => 'hello'
27
+
28
+ # required
29
+ form = UserForm.new(age: '18', address: 'somewhere')
30
+ form.valid? # => false
31
+ form.errors.full_messages # => ["Name require a value"]
32
+ ```
33
+
34
+ #### Difference between then `required` and `validates_presence_of`
35
+ `required` run before validation, it will cancel other validations if return false.
36
+
37
+ ### form object
38
+
39
+ #### API - `valid?`
40
+ Compliant with the active model api
41
+ ```ruby
42
+ class PhoneForm < ActForm::Base
43
+ attribute :phone
44
+
45
+ validates_format_of :phone, with: /\A\d{11}\z/i
46
+ end
47
+
48
+ form = PhoneForm.new
49
+ form.valid? # => false
50
+ form.errors.full_messages # => ["Phone is invalid"]
51
+
52
+ PhoneForm.new(phone: '12345678901').valid? # => true
53
+ ```
54
+
55
+ #### API - `sync`
56
+ sync only copy attributes to target, will not trigger validate
57
+ ```ruby
58
+ target = Class.new do
59
+ attr_accessor :phone
60
+ end.new
61
+
62
+ form = PhoneForm.new(phone: '12345678901')
63
+ form.sync(target)
64
+ target.phone # => '12345678901'
65
+ ```
66
+
67
+ #### API - `save`
68
+ sync to the target and call the save method when passed the validation
69
+ ```ruby
70
+ target = Class.new do
71
+ attr_accessor :phone
72
+ attr_reader :saved
73
+
74
+ def save
75
+ @saved = true
76
+ end
77
+ end.new
78
+
79
+ form = PhoneForm.new(phone: '12345678901')
80
+ form.save(target)
81
+ target.phone # => '12345678901'
82
+ target.saved # => true
83
+ form.persisted? # => true
84
+ ```
85
+
86
+ #### API - `init_by`
87
+ `init_by` will copy attributes form target to the form, and set default target.
88
+ ```ruby
89
+ target = Class.new do
90
+ attr_accessor :phone
91
+ attr_reader :saved
92
+
93
+ def save
94
+ @saved = true
95
+ end
96
+ end.new
97
+
98
+ target.phone = '12345678901'
99
+
100
+ form = PhoneForm.new
101
+ form.init_by(target)
102
+ form.save # => true
103
+ target.saved # => true
104
+ ```
105
+
106
+ #### API - `combine`
107
+ form can combine to other forms
108
+ ```ruby
109
+ class PhoneForm < ActForm::Base
110
+ attribute :phone
111
+ validates_format_of :phone, with: /\A\d{11}\z/i
112
+ end
113
+
114
+ class EmailForm < ActForm::Base
115
+ attribute :email
116
+
117
+ validate :check_email
118
+
119
+ def check_email
120
+ errors.add(:email, :blank) if email.blank?
121
+ end
122
+ end
123
+
124
+ class UserForm < ActForm::Base
125
+ combine PhoneForm, EmailForm
126
+ end
127
+
128
+ class AdminForm < ActForm::Base
129
+ combine PhoneForm
130
+ end
131
+
132
+ user_form = UserForm.new
133
+ user_form.valid?
134
+ user_form.errors.full_messages # => ["Phone is invalid", "Email can't be blank"]
135
+ UserForm.new(phone: '12345678901', email: '1').valid? # => true
136
+ admin_form = AdminForm.new
137
+ admin_form.valid?
138
+ admin_form.errors.full_messages # => ["Phone is invalid"]
139
+ AdminForm.new(phone: '12345678901').valid? # => true
140
+ ```
141
+
142
+ ### command/service object
6
143
 
144
+ Command object almost like form object. Command object can't init by `new`, and it has some new features.
145
+
146
+ #### API - `perform`, `run`, `success?`, `failure?`
147
+
148
+ command object must respond to `perform` method.
149
+
150
+ ```ruby
151
+ class CreateUserCommand < ActForm::Command
152
+ combine UserForm
153
+
154
+ def perform
155
+ # User.create(attributes)
156
+ end
157
+ end
158
+
159
+ command = CreateUserCommand.run(phone: '12345678901')
160
+ if command.success?
161
+ @user = command.result
162
+ # do something...
163
+ else
164
+ command.errors.full_messages # => ["Email can't be blank"]
165
+ # do something...
166
+ end
167
+ ```
7
168
  ## Installation
8
169
 
9
170
  Add this line to your application's Gemfile:
@@ -18,21 +179,16 @@ And then execute:
18
179
 
19
180
  Or install it yourself as:
20
181
 
21
- $ gem install form_model
22
-
23
- ## Usage
182
+ $ gem install act_form
24
183
 
25
- TODO: Write usage instructions here
26
184
 
27
185
  ## Development
28
186
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
187
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
188
 
33
189
  ## Contributing
34
190
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/form_model. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
191
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/act_form. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
192
 
37
193
 
38
194
  ## License
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = %q{A simple way to create form/command/service objects.}
13
13
  spec.description = %q{The simple way to create form objects or command/service objects with ActiveModel.}
14
- spec.homepage = 'https://github.com/simple-and-powerful/form-model'
14
+ spec.homepage = 'https://github.com/simple-and-powerful/act-form'
15
15
  spec.license = 'MIT'
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
@@ -23,12 +23,10 @@ Gem::Specification.new do |spec|
23
23
  # end
24
24
 
25
25
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
- spec.bindir = 'exe'
27
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
27
  spec.require_paths = ['lib']
29
28
 
30
- spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.0'
31
- spec.add_runtime_dependency 'activemodel', '~> 4.0', '>= 4.0.0'
29
+ spec.add_runtime_dependency 'activemodel', '~> 5.0', '>= 5.0.0'
32
30
 
33
31
  spec.add_development_dependency 'bundler', '~> 1.12'
34
32
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -7,28 +7,10 @@ module ActForm
7
7
  include Model
8
8
  end
9
9
 
10
- class RecordForm < Base
11
- attr_reader :record
12
- def initialize(record, **attrs)
13
- @record = record
14
- @extract_attrs = @record.attributes.extract! *self.class.attribute_set.map(&:name).map(&:to_s)
15
- super(@extract_attrs.merge(attrs))
16
- end
17
-
18
- def save
19
- if valid?
20
- sync(@record)
21
- @persisted = @record.save
22
- else
23
- false
24
- end
25
- end
26
- end
27
-
28
10
  class Command < Base
29
11
  include Runnable
30
12
  private_class_method :new
31
13
  end
32
14
  end
33
15
 
34
- require 'act_form/railtie' if defined?(Rails)
16
+ I18n.load_path << "#{File.dirname(__FILE__)}/act_form/locale/en.yml"
@@ -0,0 +1,57 @@
1
+ require 'act_form/type'
2
+
3
+ module ActForm
4
+ module Attributes
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :attribute_set, instance_accessor: false
9
+ self.attribute_set = {}
10
+ end
11
+
12
+ def attributes
13
+ @attributes || {}
14
+ end
15
+
16
+ private
17
+
18
+ def get_default(default, default_provided)
19
+ return if default == default_provided
20
+ default.respond_to?(:call) ? default.call : default
21
+ end
22
+
23
+ module ClassMethods
24
+ # attribute :name, type: :string
25
+ # or
26
+ # attribute :name, :string, required: true
27
+ def attribute(name, cast_type = :object, **options)
28
+ name = name.to_s
29
+ cast_type = options[:type] || cast_type
30
+ self.attribute_set = attribute_set.merge(name => [cast_type, options])
31
+
32
+ define_reader_method name, **options.slice(:default)
33
+ define_writer_method name, cast_type
34
+
35
+ name
36
+ end
37
+
38
+ def define_reader_method(name, default: NO_DEFAULT_PROVIDED)
39
+ define_method(name) { attributes[name] || get_default(default, NO_DEFAULT_PROVIDED) }
40
+ end
41
+
42
+ def define_writer_method(name, cast_type)
43
+ define_method("#{name}=") do |value|
44
+ _value = ActiveModel::Type.lookup(cast_type).deserialize(value)
45
+ @attributes = attributes.merge({name => _value})
46
+ _value
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ NO_DEFAULT_PROVIDED = Object.new
53
+ private_constant :NO_DEFAULT_PROVIDED
54
+
55
+ end # class_methods
56
+ end
57
+ end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/concern'
2
- require 'act_form/utils'
3
2
 
4
3
  module ActForm
5
4
  module Combinable
@@ -10,40 +9,38 @@ module ActForm
10
9
  self._forms = []
11
10
  end
12
11
 
12
+ def valid?(context = nil)
13
+ super
14
+ combined_forms_valid?(context)
15
+ errors.empty?
16
+ end
17
+
18
+ def combined_forms_valid?(context)
19
+ return if _forms.empty?
20
+ _forms.each do |form_class|
21
+ form = form_class.new(attributes)
22
+ form.valid?(context)
23
+ form.errors.details.each do |attr_name, arr|
24
+ arr.each do |error|
25
+ next if error[:error] == :required
26
+ errors.add(attr_name, error[:error])
27
+ end
28
+ end
29
+ end
30
+ end
31
+
13
32
  class_methods do
14
33
 
15
34
  def combine(*forms)
16
35
  forms.each do |form_class|
17
- raise ArgumentError, 'Can not combine itself' if form_class == self
36
+ raise ArgumentError, "can't combine itself" if form_class == self
18
37
 
19
38
  next if self._forms.include?(form_class)
20
39
 
40
+ self.merge_attribute_set_from(form_class)
21
41
  self._forms << form_class
22
-
23
- self.attribute_set.merge(form_class.attribute_set)
24
-
25
- Utils.merge(self._validators, form_class._validators)
26
-
27
- method_name = form_class.model_name.singular
28
- define_method(method_name) { form_class.new(attributes) }
29
- form_class._validate_callbacks.each do |callback|
30
- filter = callback.filter
31
- if filter.is_a?(Symbol) || filter.is_a?(String)
32
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
33
- def #{filter}
34
- form = #{method_name}
35
- form.send(:#{filter})
36
- form.errors.messages.each do |attr, _errors|
37
- _errors.each { |err| errors.add(attr, err) }
38
- end
39
- end
40
- private :#{filter}
41
- RUBY
42
- end
43
- end
44
- self._validate_callbacks.append *form_class._validate_callbacks
45
- end # forms.each
46
- end # combine
42
+ end
43
+ end
47
44
 
48
45
  end
49
46
 
@@ -0,0 +1,4 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ required: "require a value"
@@ -0,0 +1,15 @@
1
+ module ActForm
2
+ module Merge
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def merge_attribute_set_from(other)
7
+ other.attribute_set.each do |attr_name, arr|
8
+ cast_type, options = arr
9
+ attribute attr_name, cast_type, options
10
+ end
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -1,27 +1,39 @@
1
- require 'active_support/core_ext/object/deep_dup'
2
1
  require 'active_model'
3
- require 'virtus'
2
+ require 'act_form/attributes'
3
+ require 'act_form/merge'
4
4
  require 'act_form/combinable'
5
5
 
6
6
  module ActForm
7
7
  module Model
8
8
  extend ActiveSupport::Concern
9
- include ActiveModel::Validations
10
- include ActiveModel::Conversion
9
+ include ActiveModel::Model
10
+ include Attributes
11
+ include Merge
11
12
 
12
13
  included do
13
- extend ActiveModel::Naming
14
- extend ActiveModel::Translation
15
- include Virtus.model
14
+ set_callback :validate, :before, :validate_required_attributes
15
+ end
16
+
17
+ def initialize(attrs={})
18
+ super attrs.select { |k, _| respond_to?("#{k}=") }
19
+ end
20
+
21
+ # Record must respond_to attributes method
22
+ def init_by(record, **attrs)
23
+ @record = record
24
+ _attrs = record.attributes.extract! *self.class.attribute_set.keys.map(&:to_s)
25
+ assign_attributes _attrs.merge(attrs)
16
26
  end
17
27
 
18
28
  def sync(target)
19
- self.class.attribute_set.map(&:name).each do |attr|
20
- target.public_send("#{attr}=", public_send(attr)) if target.respond_to?(attr)
29
+ self.class.attribute_set.keys.each do |attr|
30
+ next unless target.respond_to?(attr)
31
+ target.public_send "#{attr}=", public_send(attr)
21
32
  end
22
33
  end
23
34
 
24
- def save(target)
35
+ def save(target = nil)
36
+ target ||= @record
25
37
  if valid?
26
38
  sync(target)
27
39
  @persisted = target.save
@@ -34,14 +46,25 @@ module ActForm
34
46
  !!@persisted
35
47
  end
36
48
 
49
+ private
50
+
51
+ def validate_required_attributes
52
+ self.class.attribute_set.each do |attr_name, arr|
53
+ _, options = arr
54
+ next if !options[:required]
55
+ if attributes[attr_name].nil?
56
+ errors.add(attr_name, :required)
57
+ end
58
+ end
59
+ throw(:abort) unless errors.empty?
60
+ end
61
+
37
62
  class_methods do
38
63
  private
39
64
  def inherited(child_class)
40
- child_class._validators = self._validators.deep_dup
41
- child_class._validate_callbacks = self._validate_callbacks.deep_dup
42
65
  child_class.include Combinable
43
66
  super
44
67
  end
45
- end
68
+ end # class_methods
46
69
  end
47
70
  end
@@ -0,0 +1,13 @@
1
+ require 'active_model/type'
2
+
3
+ module ActForm
4
+ module Type
5
+ class Object < ActiveModel::Type::Value
6
+ def type
7
+ :object
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ ActiveModel::Type.register(:object, ActForm::Type::Object)
@@ -1,3 +1,3 @@
1
1
  module ActForm
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,55 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: act_form
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zires
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2016-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: virtus
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.0'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 1.0.0
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: '1.0'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 1.0.0
33
13
  - !ruby/object:Gem::Dependency
34
14
  name: activemodel
35
15
  requirement: !ruby/object:Gem::Requirement
36
16
  requirements:
37
17
  - - "~>"
38
18
  - !ruby/object:Gem::Version
39
- version: '4.0'
19
+ version: '5.0'
40
20
  - - ">="
41
21
  - !ruby/object:Gem::Version
42
- version: 4.0.0
22
+ version: 5.0.0
43
23
  type: :runtime
44
24
  prerelease: false
45
25
  version_requirements: !ruby/object:Gem::Requirement
46
26
  requirements:
47
27
  - - "~>"
48
28
  - !ruby/object:Gem::Version
49
- version: '4.0'
29
+ version: '5.0'
50
30
  - - ">="
51
31
  - !ruby/object:Gem::Version
52
- version: 4.0.0
32
+ version: 5.0.0
53
33
  - !ruby/object:Gem::Dependency
54
34
  name: bundler
55
35
  requirement: !ruby/object:Gem::Requirement
@@ -94,15 +74,15 @@ files:
94
74
  - Rakefile
95
75
  - act_form.gemspec
96
76
  - lib/act_form.rb
77
+ - lib/act_form/attributes.rb
97
78
  - lib/act_form/combinable.rb
79
+ - lib/act_form/locale/en.yml
80
+ - lib/act_form/merge.rb
98
81
  - lib/act_form/model.rb
99
- - lib/act_form/railtie.rb
100
82
  - lib/act_form/runnable.rb
101
- - lib/act_form/utils.rb
83
+ - lib/act_form/type.rb
102
84
  - lib/act_form/version.rb
103
- - lib/generators/act_form/install_generator.rb
104
- - lib/generators/templates/forms/record_form.rb
105
- homepage: https://github.com/simple-and-powerful/form-model
85
+ homepage: https://github.com/simple-and-powerful/act-form
106
86
  licenses:
107
87
  - MIT
108
88
  metadata: {}
@@ -1,7 +0,0 @@
1
- module ActForm
2
- class Railtie < ::Rails::Railtie
3
- generators do
4
- require 'generators/form_model/install_generator'
5
- end
6
- end
7
- end
@@ -1,11 +0,0 @@
1
- module ActForm
2
- class Utils
3
- class << self
4
- def merge(a_validators, b_validators)
5
- b_validators.each do |k, v|
6
- a_validators[k] ? (a_validators[k] += v) : (a_validators[k] = v)
7
- end
8
- end # merge
9
- end
10
- end
11
- end
@@ -1,12 +0,0 @@
1
- module ActForm
2
- module Generators
3
- class InstallGenerator < ::Rails::Generators::Base
4
- source_root File.expand_path('../../templates', __FILE__)
5
-
6
- desc 'Create the forms dir and copy record_form.rb'
7
- def copy_initializer
8
- directory 'forms', 'app/forms'
9
- end
10
- end
11
- end
12
- end
@@ -1,2 +0,0 @@
1
- class RecordForm < ActForm::RecordForm
2
- end