attribeauty 0.4.8 → 0.4.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +236 -132
- data/attribeauty.gemspec +36 -0
- data/lib/attribeauty/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b6b12bbaf3cc5bbef6bd67f9d8c69ad35ba2608bd3c90798ef6510884940cb7
|
4
|
+
data.tar.gz: 2f86805ee0ca65397f78eab60e83cd2740e4eef03879f130679d062d996f0467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68bc689b886c754315b5b41203e67fb4ed9e46818eebc8b7e4cf336c4cae0fba92b56c57ff78a2611814db61c170980ba025bcf8ff5d9b65cb22b75a59389218
|
7
|
+
data.tar.gz: 8b023ec357bd89dabfcb17ecd317bd70e322ae822a10dc17ac0aacee49465ea33b47b2efee5cf44575fc78b87fb2444cece241f49e6a320e84feb363eac33f89
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,152 +1,170 @@
|
|
1
1
|
# Attribeauty
|
2
2
|
|
3
|
-
|
4
|
-
There are so many of these (notably rails Attributes api), but none were what I wanted.
|
3
|
+
## Params
|
5
4
|
|
6
|
-
|
5
|
+
`Attribeauty::Params` casts your params and removes elements you want to exclude if they are nil or empty.
|
7
6
|
|
8
|
-
|
7
|
+
#### Why is this needed?
|
9
8
|
|
10
|
-
|
9
|
+
`params` arrive in your controllers as strings—whether they represent integers, nil, dates, or anything else. Rails handles coercion at the Model level when these params are assigned as attributes. However, there are often many steps before your params are assigned. `Attribeauty::Params` elegantly ensures that your attributes start in their expected state before continuing their journey to their final destination.
|
11
10
|
|
12
|
-
|
11
|
+
#### Directions
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
## Base
|
17
|
-
|
18
|
-
Inherit from `Attribeauty::Base` and then add attribute with the type you want.
|
19
|
-
Initialize with these and they will be cast to that attribute.
|
20
|
-
Use `assign_attributes` to update the object.
|
13
|
+
First, let's set a `params_filter` object to accept rails `params.to_unsafe_h` in the `ApplicationController`
|
21
14
|
|
15
|
+
```ruby
|
16
|
+
# app/controllers/application_controller.rb
|
17
|
+
class ApplicationController
|
18
|
+
private
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
attribute :second, :integer
|
27
|
-
attribute :third, :float
|
28
|
-
attribute :forth, :boolean
|
29
|
-
attribute :fifth, :time
|
30
|
-
attribute :sixth, :koala
|
31
|
-
attribute :seventh, :string, default: "Kangaroo"
|
20
|
+
def params_filter
|
21
|
+
Attribeauty::Params.with(params.to_unsafe_h)
|
22
|
+
end
|
32
23
|
end
|
33
|
-
|
34
|
-
instance = MyClass.new(first: 456)
|
35
|
-
instance.first # => "456"
|
36
|
-
instance.assign_attributes(second: "456")
|
37
|
-
instance.second # => 456
|
38
|
-
instance.first = 9000
|
39
|
-
instance.first # => "9000"
|
40
|
-
instance.seventh # => "Kangaroo"
|
41
24
|
```
|
42
25
|
|
43
|
-
|
44
|
-
|
26
|
+
The `params_filter` object here will take any ruby hash, `symbolize` the keys, and is now ready for the structure you want to provide.
|
27
|
+
|
28
|
+
If a users controller receives the following params:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
{
|
32
|
+
'user' => {
|
33
|
+
'username' => 'js_bach',
|
34
|
+
'full_name' => 'Johann Sebastian Bach',
|
35
|
+
'job_title' => 'Composer',
|
36
|
+
'age' => '43',
|
37
|
+
'salary' => nil,
|
38
|
+
'email' => {
|
39
|
+
'address' => 'js@bach.music'
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
45
43
|
```
|
46
|
-
Attribeauty.configure do |config|
|
47
|
-
config.types[:koala] = MyTypes::Koala
|
48
|
-
end
|
49
44
|
|
50
|
-
|
51
|
-
class Koala
|
52
|
-
def cast(value)
|
53
|
-
value.inspect.to_s << "_koalas"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
45
|
+
We can coerce them with into `create_params` with the following:
|
57
46
|
|
58
|
-
class MyClass < Attribeauty::Base
|
59
|
-
attribute :wild_animal, :koala
|
60
|
-
end
|
61
47
|
|
62
|
-
|
63
|
-
|
48
|
+
```ruby
|
49
|
+
# app/controllers/my_controller.rb
|
50
|
+
class UsersController < ApplicationController
|
51
|
+
def edit; end
|
64
52
|
|
65
|
-
|
53
|
+
def update
|
54
|
+
@user = Users::Creator.call(create_params)
|
66
55
|
|
67
|
-
|
68
|
-
|
69
|
-
|
56
|
+
if @user.valid?
|
57
|
+
redirect_to index_path, notice: 'Welcome to the app'
|
58
|
+
else
|
59
|
+
flash[:alert] = @user.errors.full_messages
|
60
|
+
render :edit
|
61
|
+
end
|
62
|
+
end
|
70
63
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
64
|
+
private
|
65
|
+
|
66
|
+
def create_params
|
67
|
+
params_filter.accept do
|
68
|
+
root :user do
|
69
|
+
attribute :username, :string, required: true
|
70
|
+
attribute :full_name, :string
|
71
|
+
attribute :job_title, :string, exclude_if: [:nil?, :empty?]
|
72
|
+
attribute :age, :integer
|
73
|
+
attribute :salary, :integer, exclude_if: :nil?
|
74
|
+
attribute :email do
|
75
|
+
attribute :address, :string, required: true
|
76
|
+
attribute :receive_updates, :boolean, default: false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
80
|
end
|
81
81
|
end
|
82
|
-
|
83
82
|
```
|
84
83
|
|
85
|
-
|
84
|
+
The above will return a hash with the `age` integer cast to integer, the `salary` removed, and a `receive_updates` defaulted to `false`. The `root` `user` node will be removed too. If you wish to keep the root node, simply using `attribute` with a `block` will suffice. Below is the output from this:
|
86
85
|
|
87
|
-
Experimental params sanitizer is now available. This will cast your params, and remove elements you want to exclude if `nil` or `empty`
|
88
86
|
|
89
|
-
|
87
|
+
```ruby
|
88
|
+
{
|
89
|
+
'username' => 'js_bach',
|
90
|
+
'full_name' => 'Johann Sebastian Bach',
|
91
|
+
'job_title' => 'Composer',
|
92
|
+
'age' => 43,
|
93
|
+
'email' => {
|
94
|
+
'address' => 'js@bach.music',
|
95
|
+
'receive_updates' => false
|
96
|
+
}
|
97
|
+
}
|
90
98
|
|
91
99
|
```
|
92
|
-
# app/controllers/my_controller.rb
|
93
|
-
class MyController
|
94
|
-
def update
|
95
|
-
MyRecord.update(update_params)
|
96
|
-
|
97
|
-
redirect_to index_path
|
98
|
-
end
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
`Attribeauty::Params` can handle nested arrays and nested hashes with the same `accept`:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# {
|
105
|
+
# "username" => "js_bach",
|
106
|
+
# "full_name" => "Johann Sebastian Bach",
|
107
|
+
# "job_title" => "Composer",
|
108
|
+
# "age" => 43,
|
109
|
+
# "email" => [
|
110
|
+
# { "address" => "js@bach.music", "secondary" => false },
|
111
|
+
# { "address" => "papa@bach.music", "secondary" => true }
|
112
|
+
# ]
|
113
|
+
# }
|
114
|
+
#
|
115
|
+
# or
|
116
|
+
#
|
117
|
+
# {
|
118
|
+
# "username" => "js_bach",
|
119
|
+
# "full_name" => "Johann Sebastian Bach",
|
120
|
+
# "job_title" => "Composer",
|
121
|
+
# "age" => 43,
|
122
|
+
# "email" => { "address" => "js@bach.music", "secondary" => false }
|
123
|
+
# }
|
124
|
+
def create_params
|
107
125
|
params_filter.accept do
|
108
|
-
attribute :
|
126
|
+
attribute :username, :string, required: true
|
127
|
+
attribute :full_name, :string
|
128
|
+
attribute :job_title, :string, exclude_if: [:nil?, :empty?]
|
129
|
+
attribute :age, :integer
|
130
|
+
attribute :salary, :integer, exclude_if: :nil?
|
109
131
|
attribute :email do
|
110
|
-
attribute :address, :string
|
111
|
-
attribute :
|
112
|
-
attribute :ip_address, :string, exclude_if: :empty?
|
132
|
+
attribute :address, :string, required: true
|
133
|
+
attribute :secondary, :boolean, default: false
|
113
134
|
end
|
114
135
|
end
|
115
136
|
end
|
116
137
|
```
|
117
138
|
|
118
|
-
|
119
|
-
`exclude_if` will accept a single method call (`:nil?`) or an array (`[:nil?, :empty?]`)
|
139
|
+
#### Error handling
|
120
140
|
|
121
|
-
|
141
|
+
`Attribeauty::Params` has rudimentary error handling, and will return an errors array when `required: true` values are missing:
|
142
|
+
|
143
|
+
```ruby
|
122
144
|
class MyController
|
145
|
+
def edit; end
|
146
|
+
|
123
147
|
def update
|
124
|
-
|
125
|
-
|
126
|
-
|
148
|
+
if params_filter.errors.any?
|
149
|
+
flash[:alert] = params.errors.join(', ')
|
150
|
+
render :edit
|
151
|
+
else
|
152
|
+
MyRecord::Updater.call(update_params)
|
153
|
+
redirect_to index_path
|
154
|
+
end
|
127
155
|
end
|
128
156
|
|
129
157
|
private
|
130
158
|
|
131
|
-
#
|
132
|
-
# { user: {
|
133
|
-
|
134
|
-
|
135
|
-
Attribeauty::Params.with(request.params)
|
136
|
-
end
|
159
|
+
# with the following params:
|
160
|
+
# { user: { username: nil } }
|
161
|
+
|
162
|
+
# update_params.errors => ["username required"]
|
137
163
|
|
138
|
-
# using root will exclude the value and yield:
|
139
|
-
# { title: "woo", email: { address: "hmm@yep.com" } }
|
140
|
-
#
|
141
164
|
def update_params
|
142
165
|
params_filter.accept do
|
143
166
|
root :user do
|
144
|
-
attribute :
|
145
|
-
attribute :email do
|
146
|
-
attribute :address, :string, exclude_if: [:empty?, :nil?]
|
147
|
-
attribute :valid, :boolean
|
148
|
-
attribute :ip_address, :string, exclude_if: :empty?
|
149
|
-
end
|
167
|
+
attribute :username, :string, required: true
|
150
168
|
end
|
151
169
|
end
|
152
170
|
end
|
@@ -154,38 +172,30 @@ end
|
|
154
172
|
|
155
173
|
```
|
156
174
|
|
175
|
+
#### Raising Errors
|
176
|
+
|
157
177
|
If you want to raise an error, rather than just return the errors in an array, use the `accept!` method. Will raise `Attribeauty::MissingAttributeError` with the required elements:
|
158
178
|
|
159
179
|
|
160
|
-
```
|
180
|
+
```ruby
|
161
181
|
class MyController
|
162
182
|
def update
|
163
|
-
MyRecord.
|
164
|
-
|
183
|
+
MyRecord::Updater.call(update_params)
|
184
|
+
# calling update_params
|
185
|
+
# will raise: Attribeauty::MissingAttributeError, "username required"
|
186
|
+
|
165
187
|
redirect_to index_path
|
166
188
|
end
|
167
189
|
|
168
190
|
private
|
169
191
|
|
170
|
-
#
|
171
|
-
# { user: {
|
172
|
-
#
|
173
|
-
def params_filter
|
174
|
-
Attribeauty::Params.with(request.params)
|
175
|
-
end
|
192
|
+
# with the following params:
|
193
|
+
# { user: { username: nil } }
|
176
194
|
|
177
|
-
# This following with the accept! method
|
178
|
-
# will raise: Attribeauty::MissingAttributeError, "title required, email required"
|
179
|
-
#
|
180
195
|
def update_params
|
181
196
|
params_filter.accept! do
|
182
197
|
root :user do
|
183
|
-
attribute :
|
184
|
-
attribute :email do
|
185
|
-
attribute :address, :string
|
186
|
-
attribute :valid, :boolean
|
187
|
-
attribute :ip_address, :string
|
188
|
-
end
|
198
|
+
attribute :username, :string, required: true
|
189
199
|
end
|
190
200
|
end
|
191
201
|
end
|
@@ -193,10 +203,12 @@ end
|
|
193
203
|
|
194
204
|
```
|
195
205
|
|
196
|
-
|
197
|
-
You can also exclude a value from this by useing the `allows` option.
|
206
|
+
#### Require all
|
198
207
|
|
199
|
-
|
208
|
+
What if you want to require all attributes? If you pass the `required: true` or `exclude_if: :nil?` with the `accept`, it will be applied to all attributes.
|
209
|
+
You can also exclude a value from this by using the `allows` option.
|
210
|
+
|
211
|
+
```ruby
|
200
212
|
class MyController
|
201
213
|
def update
|
202
214
|
MyRecord.update(update_params)
|
@@ -206,17 +218,13 @@ class MyController
|
|
206
218
|
|
207
219
|
private
|
208
220
|
|
209
|
-
#
|
221
|
+
# with the following params:
|
210
222
|
# { user: { profile: [{ address: { street_name: "Main St" } }] } }
|
211
|
-
#
|
212
|
-
def params_filter
|
213
|
-
Attribeauty::Params.with(request.params)
|
214
|
-
end
|
215
223
|
|
216
|
-
#
|
217
|
-
|
224
|
+
# required: true will be passed onto all attributes, except ip_address
|
225
|
+
|
218
226
|
def update_params
|
219
|
-
params_filter.accept
|
227
|
+
params_filter.accept required: true do
|
220
228
|
root :user do
|
221
229
|
attribute :title, :string,
|
222
230
|
attribute :email do
|
@@ -234,6 +242,102 @@ end
|
|
234
242
|
See `test/test_params.rb` for more examples
|
235
243
|
|
236
244
|
|
245
|
+
## Base
|
246
|
+
|
247
|
+
I needed a straightforward way to initialize mutable objects, and this solution provides exactly that. While there are many existing options (notably the Rails Attributes API), I opted to build my own.
|
248
|
+
|
249
|
+
|
250
|
+
Inherit from `Attribeauty::Base` and then add attribute with the type you want.
|
251
|
+
Initialize with these and they will be cast to that attribute.
|
252
|
+
Use `assign_attributes` to update the object.
|
253
|
+
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
class MyClass < Attribeauty::Base
|
257
|
+
attribute :first, :string
|
258
|
+
attribute :second, :integer
|
259
|
+
attribute :third, :float
|
260
|
+
attribute :forth, :boolean
|
261
|
+
attribute :fifth, :time
|
262
|
+
attribute :sixth, :koala
|
263
|
+
attribute :seventh, :string, default: "Kangaroo"
|
264
|
+
end
|
265
|
+
|
266
|
+
instance = MyClass.new(first: 456)
|
267
|
+
instance.first # => "456"
|
268
|
+
instance.assign_attributes(second: "456")
|
269
|
+
instance.second # => 456
|
270
|
+
instance.first = 9000
|
271
|
+
instance.first # => "9000"
|
272
|
+
instance.seventh # => "Kangaroo"
|
273
|
+
```
|
274
|
+
|
275
|
+
To add your own types, simply have a class that handles `MyClassName.new.cast(value)`:
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
Attribeauty.configure do |config|
|
279
|
+
config.types[:koala] = MyTypes::Koala
|
280
|
+
end
|
281
|
+
|
282
|
+
module MyTypes
|
283
|
+
class Koala
|
284
|
+
def cast(value)
|
285
|
+
value.inspect.to_s << "_koalas"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
class MyClass < Attribeauty::Base
|
291
|
+
attribute :wild_animal, :koala
|
292
|
+
end
|
293
|
+
|
294
|
+
instance = MyClass.new(wild_animal: "the_wildest_animals_are")
|
295
|
+
instance.wild_animal # => "the_wildest_animals_are_koalas"
|
296
|
+
|
297
|
+
```
|
298
|
+
|
299
|
+
To use rails types add to your config:
|
300
|
+
```ruby
|
301
|
+
# config/initializers/attribeauty.rb
|
302
|
+
|
303
|
+
Rails.application.reloader.to_prepare do
|
304
|
+
Attribeauty.configure do |config|
|
305
|
+
config.types[:string] = ActiveModel::Type::String
|
306
|
+
config.types[:boolean] = ActiveModel::Type::Boolean
|
307
|
+
config.types[:date] = ActiveModel::Type::Date
|
308
|
+
config.types[:time] = ActiveModel::Type::Time
|
309
|
+
config.types[:datetime] = ActiveModel::Type::DateTime
|
310
|
+
config.types[:float] = ActiveModel::Type::Float
|
311
|
+
config.types[:integer] = ActiveModel::Type::Integer
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
```
|
316
|
+
|
317
|
+
#### Is this for rails only?
|
318
|
+
Nope, any ruby program will work with this.
|
319
|
+
|
320
|
+
## Installation
|
321
|
+
|
322
|
+
Add `attribeauty` to your application's Gemfile and `bundle install` the gem:
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
# Gemfile
|
326
|
+
gem 'attribeauty'
|
327
|
+
```
|
328
|
+
|
329
|
+
Use bundle to automatically install the gem and add to the application's Gemfile by executing:
|
330
|
+
|
331
|
+
```bash
|
332
|
+
$ bundle add attribeauty
|
333
|
+
```
|
334
|
+
|
335
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
336
|
+
|
337
|
+
```bash
|
338
|
+
$ gem install attribeauty
|
339
|
+
```
|
340
|
+
|
237
341
|
## Development
|
238
342
|
|
239
343
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/attribeauty.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/attribeauty/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "attribeauty"
|
7
|
+
spec.version = Attribeauty::VERSION
|
8
|
+
spec.authors = ["Toby"]
|
9
|
+
spec.email = ["toby@darkroom.tech"]
|
10
|
+
|
11
|
+
spec.summary = "Attributes simply done"
|
12
|
+
spec.description = "There are so many of these, I just needed this one."
|
13
|
+
spec.homepage = "https://github.com/tobyond/attribeauty"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/tobyond/attribeauty"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Uncomment to register a new dependency of your gem
|
32
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, check out our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
data/lib/attribeauty/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attribeauty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Toby
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: There are so many of these, I just needed this one.
|
14
14
|
email:
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- LICENSE.txt
|
31
31
|
- README.md
|
32
32
|
- Rakefile
|
33
|
+
- attribeauty.gemspec
|
33
34
|
- lib/attribeauty.rb
|
34
35
|
- lib/attribeauty/base.rb
|
35
36
|
- lib/attribeauty/configuration.rb
|