attribeauty 0.4.7 → 0.4.11

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
  SHA256:
3
- metadata.gz: c3e7fb42086d75e28fa5c80ff5718c7de734759a0bce85df5b3fd48a2fabbbaf
4
- data.tar.gz: 2a08c818e591cd8b4a780d275f2323c76842c7095a2de878bda7bfaab357ef0c
3
+ metadata.gz: 2b6b12bbaf3cc5bbef6bd67f9d8c69ad35ba2608bd3c90798ef6510884940cb7
4
+ data.tar.gz: 2f86805ee0ca65397f78eab60e83cd2740e4eef03879f130679d062d996f0467
5
5
  SHA512:
6
- metadata.gz: 970ede789c687fd334b84555b610940e3862672166269a2b39055c942f79a7466371f4a05d35bc658b5b455914281e5cde607e9a41da84c49e7554d58df935b7
7
- data.tar.gz: 2e7a482d54b2487169cb63380eb641a42a3bc03a1fecd25028ed917e2184110bdd658173924de55ff7440c7101c3a93b5f42bf182f2e58c90a6e874ef4406a86
6
+ metadata.gz: 68bc689b886c754315b5b41203e67fb4ed9e46818eebc8b7e4cf336c4cae0fba92b56c57ff78a2611814db61c170980ba025bcf8ff5d9b65cb22b75a59389218
7
+ data.tar.gz: 8b023ec357bd89dabfcb17ecd317bd70e322ae822a10dc17ac0aacee49465ea33b47b2efee5cf44575fc78b87fb2444cece241f49e6a320e84feb363eac33f89
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.9] - 2024-07-02
4
+
5
+ - Updated README
6
+
7
+ ## [0.4.8] - 2024-07-01
8
+
9
+ - allow args with base, so `required` and `default` can be used
10
+
3
11
  ## [0.4.7] - 2024-07-01
4
12
 
5
13
  - generate for fix, and added strict option.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- attribeauty (0.4.7)
4
+ attribeauty (0.4.11)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,150 +1,170 @@
1
1
  # Attribeauty
2
2
 
3
- I just wanted a quick, simple way to initialize mutable objects. This is it.
4
- There are so many of these (notably rails Attributes api), but none were what I wanted.
3
+ ## Params
5
4
 
6
- ## Installation
5
+ `Attribeauty::Params` casts your params and removes elements you want to exclude if they are nil or empty.
7
6
 
8
- Install the gem and add to the application's Gemfile by executing:
7
+ #### Why is this needed?
9
8
 
10
- $ bundle add attribeauty
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
- If bundler is not being used to manage dependencies, install the gem by executing:
11
+ #### Directions
13
12
 
14
- $ gem install attribeauty
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
- class MyClass < Attribeauty::Base
25
- attribute :first, :string
26
- attribute :second, :integer
27
- attribute :third, :float
28
- attribute :forth, :boolean
29
- attribute :fifth, :time
30
- attribute :sixth, :koala
20
+ def params_filter
21
+ Attribeauty::Params.with(params.to_unsafe_h)
22
+ end
31
23
  end
32
-
33
- instance = MyClass.new(first: 456)
34
- instance.first # => "456"
35
- instance.assign_attributes(second: "456")
36
- instance.second # => 456
37
- instance.first = 9000
38
- instance.first # => "9000"
39
24
  ```
40
25
 
41
- To add your own types, simply have a class that handles `MyClassName.new.cast(value)`:
42
-
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
+ }
43
43
  ```
44
- Attribeauty.configure do |config|
45
- config.types[:koala] = MyTypes::Koala
46
- end
47
44
 
48
- module MyTypes
49
- class Koala
50
- def cast(value)
51
- value.inspect.to_s << "_koalas"
52
- end
53
- end
54
- end
45
+ We can coerce them with into `create_params` with the following:
55
46
 
56
- class MyClass < Attribeauty::Base
57
- attribute :wild_animal, :koala
58
- end
59
47
 
60
- instance = MyClass.new(wild_animal: "the_wildest_animals_are")
61
- instance.wild_animal # => "the_wildest_animals_are_koalas"
48
+ ```ruby
49
+ # app/controllers/my_controller.rb
50
+ class UsersController < ApplicationController
51
+ def edit; end
62
52
 
63
- ```
53
+ def update
54
+ @user = Users::Creator.call(create_params)
64
55
 
65
- To use rails types add to your config:
66
- ```
67
- # config/initializers/attribeauty.rb
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
68
63
 
69
- Rails.application.reloader.to_prepare do
70
- Attribeauty.configure do |config|
71
- config.types[:string] = ActiveModel::Type::String
72
- config.types[:boolean] = ActiveModel::Type::Boolean
73
- config.types[:date] = ActiveModel::Type::Date
74
- config.types[:time] = ActiveModel::Type::Time
75
- config.types[:datetime] = ActiveModel::Type::DateTime
76
- config.types[:float] = ActiveModel::Type::Float
77
- config.types[:integer] = ActiveModel::Type::Integer
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
78
80
  end
79
81
  end
80
-
81
82
  ```
82
83
 
83
- ## Params
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:
84
85
 
85
- Experimental params sanitizer is now available. This will cast your params, and remove elements you want to exclude if `nil` or `empty`
86
86
 
87
- Why is this needed? Params arrive into the controller in a messy state. Booleans are not ready for caparison, integers are often strings, empty strings, and nils abound. Rails does the casting of params at the model, which is simple and elegant, but in many cases, these params are used for a multitude of things before hitting the database. I truly believe we need to cast them before they do anything.
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
+ }
88
98
 
89
99
  ```
90
- # app/controllers/my_controller.rb
91
- class MyController
92
- def update
93
- MyRecord.update(update_params)
94
-
95
- redirect_to index_path
96
- end
97
100
 
98
- private
99
-
100
- def params_filter
101
- Attribeauty::Params.with(request.params)
102
- end
103
-
104
- def update_params
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
105
125
  params_filter.accept do
106
- attribute :title, :string, required: true
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?
107
131
  attribute :email do
108
- attribute :address, :string
109
- attribute :valid, :boolean
110
- attribute :ip_address, :string, exclude_if: :empty?
132
+ attribute :address, :string, required: true
133
+ attribute :secondary, :boolean, default: false
111
134
  end
112
135
  end
113
136
  end
114
137
  ```
115
138
 
116
- If you have a "head" param, like in rails, you can exclude it, also note the `exclude_if` option, this will exclude the value completely, if it evaluates to true.
117
- `exclude_if` will accept a single method call (`:nil?`) or an array (`[:nil?, :empty?]`)
139
+ #### Error handling
118
140
 
119
- ```
141
+ `Attribeauty::Params` has rudimentary error handling, and will return an errors array when `required: true` values are missing:
142
+
143
+ ```ruby
120
144
  class MyController
145
+ def edit; end
146
+
121
147
  def update
122
- MyRecord.update(update_params)
123
-
124
- redirect_to index_path
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
125
155
  end
126
156
 
127
157
  private
128
158
 
129
- # your params look like this:
130
- # { user: { title: "woo", email: { address: "hmm@yep.com", ip_address: "" } } }
131
- #
132
- def params_filter
133
- Attribeauty::Params.with(request.params)
134
- end
159
+ # with the following params:
160
+ # { user: { username: nil } }
161
+
162
+ # update_params.errors => ["username required"]
135
163
 
136
- # using root will exclude the value and yield:
137
- # { title: "woo", email: { address: "hmm@yep.com" } }
138
- #
139
164
  def update_params
140
165
  params_filter.accept do
141
166
  root :user do
142
- attribute :title, :string, required: true
143
- attribute :email do
144
- attribute :address, :string, exclude_if: [:empty?, :nil?]
145
- attribute :valid, :boolean
146
- attribute :ip_address, :string, exclude_if: :empty?
147
- end
167
+ attribute :username, :string, required: true
148
168
  end
149
169
  end
150
170
  end
@@ -152,38 +172,30 @@ end
152
172
 
153
173
  ```
154
174
 
175
+ #### Raising Errors
176
+
155
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:
156
178
 
157
179
 
158
- ```
180
+ ```ruby
159
181
  class MyController
160
182
  def update
161
- MyRecord.update(update_params)
162
-
183
+ MyRecord::Updater.call(update_params)
184
+ # calling update_params
185
+ # will raise: Attribeauty::MissingAttributeError, "username required"
186
+
163
187
  redirect_to index_path
164
188
  end
165
189
 
166
190
  private
167
191
 
168
- # your params look like this:
169
- # { user: { profile: [{ address: { street_name: "Main St" } }] } }
170
- #
171
- def params_filter
172
- Attribeauty::Params.with(request.params)
173
- end
192
+ # with the following params:
193
+ # { user: { username: nil } }
174
194
 
175
- # This following with the accept! method
176
- # will raise: Attribeauty::MissingAttributeError, "title required, email required"
177
- #
178
195
  def update_params
179
196
  params_filter.accept! do
180
197
  root :user do
181
- attribute :title, :string, exclude_if: :nil?, required: true
182
- attribute :email do
183
- attribute :address, :string
184
- attribute :valid, :boolean
185
- attribute :ip_address, :string
186
- end
198
+ attribute :username, :string, required: true
187
199
  end
188
200
  end
189
201
  end
@@ -191,10 +203,12 @@ end
191
203
 
192
204
  ```
193
205
 
194
- 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.
195
- You can also exclude a value from this by useing the `allows` option.
206
+ #### Require all
196
207
 
197
- ```
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
198
212
  class MyController
199
213
  def update
200
214
  MyRecord.update(update_params)
@@ -204,17 +218,13 @@ class MyController
204
218
 
205
219
  private
206
220
 
207
- # your params look like this:
221
+ # with the following params:
208
222
  # { user: { profile: [{ address: { street_name: "Main St" } }] } }
209
- #
210
- def params_filter
211
- Attribeauty::Params.with(request.params)
212
- end
213
223
 
214
- # exclude_if and required will be passed onto all attributes
215
- #
224
+ # required: true will be passed onto all attributes, except ip_address
225
+
216
226
  def update_params
217
- params_filter.accept exclude_if: :nil?, required: true do
227
+ params_filter.accept required: true do
218
228
  root :user do
219
229
  attribute :title, :string,
220
230
  attribute :email do
@@ -232,6 +242,102 @@ end
232
242
  See `test/test_params.rb` for more examples
233
243
 
234
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
+
235
341
  ## Development
236
342
 
237
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.
@@ -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
@@ -4,14 +4,18 @@ module Attribeauty
4
4
  # base class to inherit from
5
5
  # class MyClass < Attribeauty::Base
6
6
  class Base
7
- def self.attribute(name, type)
7
+ def self.attribute(name, type, **args)
8
8
  @attributes ||= {}
9
9
  return if @attributes[name]
10
10
 
11
11
  @attributes[name] = type
12
12
 
13
13
  class_eval(<<-CODE, __FILE__, __LINE__ + 1)
14
- def #{name}=(value); @#{name} = TypeCaster.run(value, #{type.inspect}); end
14
+ def #{name}=(value)
15
+ validator = Validator.run(#{name.inspect}, value, #{type.inspect}, #{args})
16
+ raise MissingAttributeError, validator.errors.join(', ') if validator.errors.any?
17
+ @#{name} = validator.value
18
+ end
15
19
 
16
20
  def #{name};@#{name};end
17
21
  CODE
@@ -71,6 +71,9 @@ module Attribeauty
71
71
  # in Rails if you have a user model you can call
72
72
  # Attribeauty::Params.with(params.to_unsafe_h).generate_for(User, :username, :name, :age, :email)
73
73
  # It will grab the type, and add an exclude_if: for all with Null false
74
+ # Note, there are very few circumstances where you wouldn't want to just assign_attributes
75
+ # to the model, and use the types from there, but here you go.
76
+ # This api will never be tested or documented.
74
77
  def generate_for(model, *columns)
75
78
  raise "Method requires Rails" unless defined?(Rails)
76
79
 
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Attribeauty
4
4
  class Validator
5
- def self.run(name, type, original_val, **args)
6
- new(name, type, original_val, **args).run
5
+ def self.run(name, original_val, type = nil, args = {})
6
+ new(name, original_val, type, args).run
7
7
  end
8
8
 
9
9
  attr_reader :original_val, :errors, :name, :type, :required,
10
10
  :default, :excludes, :value, :valid, :allows
11
11
 
12
- def initialize(name, original_val, type = nil, **args)
12
+ def initialize(name, original_val, type = nil, args = {})
13
13
  @name = name
14
14
  @type = type
15
15
  @original_val = original_val
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Attribeauty
4
- VERSION = "0.4.7"
4
+ VERSION = "0.4.11"
5
5
  end
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.7
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-07-01 00:00:00.000000000 Z
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