act_form 0.1.0 → 0.2.0

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: 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