ru.Bee 2.1.0 → 2.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 +4 -4
- data/lib/db/create_clients.rb +15 -0
- data/lib/rubee/controllers/extensions/auth_tokenable.rb +8 -8
- data/lib/rubee/extensions/validatable.rb +45 -6
- data/lib/rubee.rb +1 -1
- data/lib/tests/controllers/auth_tokenable_test.rb +80 -8
- data/lib/tests/example_models/client.rb +3 -0
- data/lib/tests/models/account_model_test.rb +2 -3
- data/lib/tests/models/comment_model_test.rb +47 -5
- data/lib/tests/test.db +0 -0
- data/readme.md +66 -48
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5d297ac622918eccf7bd285f4cfdf848e2909cbd19f57378105022505dfd222
|
|
4
|
+
data.tar.gz: 8976743ad813675bdd5c32d9149c5ffd70c3254980ab4a3b25835532e852d0cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d9053f14123576c2a28e58fe39398a207bd5b350e96b871bf291f24ae9470ec15659cfbbbce643fb54a689fb9f45199792b571a3be4634397c7988bc5e9b42d
|
|
7
|
+
data.tar.gz: 3f2572054237820d93a8cec5daee95f86df00e36991b74e74fa1b5cf5bfeffc088c96d01026d07a1d163486fc8ad47d594c3f484396e8b467832e2946adea869
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateClients
|
|
2
|
+
def call
|
|
3
|
+
return if Rubee::SequelObject::DB.tables.include?(:clients)
|
|
4
|
+
|
|
5
|
+
Rubee::SequelObject::DB.create_table(:clients) do
|
|
6
|
+
primary_key(:id)
|
|
7
|
+
String(:name)
|
|
8
|
+
String(:digest_password)
|
|
9
|
+
index(:name)
|
|
10
|
+
# timestamps
|
|
11
|
+
datetime(:created)
|
|
12
|
+
datetime(:updated)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -3,7 +3,7 @@ require 'date'
|
|
|
3
3
|
|
|
4
4
|
module Rubee
|
|
5
5
|
module AuthTokenable
|
|
6
|
-
KEY =
|
|
6
|
+
KEY ="secret#{ENV['JWT_KEY']}#{Date.today}".freeze unless defined?(KEY) # Feel free to cusomtize it
|
|
7
7
|
EXPIRE = 3600 unless defined?(EXPIRE)
|
|
8
8
|
|
|
9
9
|
def self.included(base)
|
|
@@ -27,10 +27,10 @@ module Rubee
|
|
|
27
27
|
@request.env['rack.session']&.[]('authentificated')
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def authentificated_user
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@authentificated_user ||=
|
|
30
|
+
def authentificated_user(user_model: ::User, login: :email, password: :password)
|
|
31
|
+
if params[login] && params[password]
|
|
32
|
+
query_params = { login => params[login], password => params[password] }
|
|
33
|
+
@authentificated_user ||= user_model.where(query_params).first
|
|
34
34
|
elsif @request.cookies['jwt'] && valid_token?
|
|
35
35
|
token = @request.cookies['jwt']
|
|
36
36
|
hash = ::JWT.decode(token, Rubee::AuthTokenable::KEY, true, { algorithm: 'HS256' })
|
|
@@ -38,11 +38,11 @@ module Rubee
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def authentificate!
|
|
42
|
-
return false unless authentificated_user
|
|
41
|
+
def authentificate!(user_model: ::User, login: :email, password: :password)
|
|
42
|
+
return false unless authentificated_user(user_model:, login:, password:)
|
|
43
43
|
|
|
44
44
|
# Generate token
|
|
45
|
-
payload = { username: params[
|
|
45
|
+
payload = { username: params[login], exp: Time.now.to_i + EXPIRE }
|
|
46
46
|
@token = ::JWT.encode(payload, KEY, 'HS256')
|
|
47
47
|
# Set jwt token to the browser within cookie, so next browser request will include it.
|
|
48
48
|
# make sure it passed to response_with headers options
|
|
@@ -31,44 +31,73 @@ module Rubee
|
|
|
31
31
|
@optional = false
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def required(
|
|
34
|
+
def required(error_message = nil)
|
|
35
35
|
value = @instance.send(@attribute)
|
|
36
|
+
|
|
37
|
+
error_hash = assemble_error_hash(error_message, :required, attribute: @attribute)
|
|
36
38
|
if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
37
39
|
@state.add_error(@attribute, error_hash)
|
|
38
40
|
end
|
|
41
|
+
|
|
39
42
|
self
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
def optional(*)
|
|
43
46
|
@optional = true
|
|
47
|
+
|
|
44
48
|
self
|
|
45
49
|
end
|
|
46
50
|
|
|
47
|
-
def
|
|
48
|
-
|
|
51
|
+
def attribute
|
|
52
|
+
self
|
|
53
|
+
end
|
|
49
54
|
|
|
55
|
+
def type(expected_class, error_message = nil)
|
|
56
|
+
return self if @state.has_errors_for?(@attribute)
|
|
50
57
|
value = @instance.send(@attribute)
|
|
51
58
|
return self if @optional && value.nil?
|
|
52
59
|
|
|
60
|
+
error_hash = assemble_error_hash(error_message, :type, class: expected_class)
|
|
53
61
|
unless value.is_a?(expected_class)
|
|
54
62
|
@state.add_error(@attribute, error_hash)
|
|
55
63
|
end
|
|
64
|
+
|
|
56
65
|
self
|
|
57
66
|
end
|
|
58
67
|
|
|
59
|
-
def condition(handler, error_message)
|
|
68
|
+
def condition(handler, error_message = nil)
|
|
60
69
|
return self if @state.has_errors_for?(@attribute)
|
|
61
70
|
value = @instance.send(@attribute)
|
|
62
71
|
return self if @optional && value.nil?
|
|
63
72
|
|
|
73
|
+
error_hash = assemble_error_hash(error_message, :condition)
|
|
64
74
|
if handler.respond_to?(:call)
|
|
65
|
-
@state.add_error(@attribute,
|
|
75
|
+
@state.add_error(@attribute, error_hash) unless handler.call
|
|
66
76
|
else
|
|
67
77
|
@instance.send(handler)
|
|
68
78
|
end
|
|
69
79
|
|
|
70
80
|
self
|
|
71
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def assemble_error_hash(error_message, error_type, **options)
|
|
86
|
+
error_message ||= default_message(error_type, **options)
|
|
87
|
+
if error_message.is_a?(String)
|
|
88
|
+
error_message = { message: error_message }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
error_message
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def default_message(type, **options)
|
|
95
|
+
{
|
|
96
|
+
condition: "condition is not met",
|
|
97
|
+
required: "attribute '#{options[:attribute]}' is required",
|
|
98
|
+
type: "attribute must be #{options[:class]}",
|
|
99
|
+
}[type]
|
|
100
|
+
end
|
|
72
101
|
end
|
|
73
102
|
|
|
74
103
|
def self.included(base)
|
|
@@ -102,7 +131,17 @@ module Rubee
|
|
|
102
131
|
|
|
103
132
|
def run_validations
|
|
104
133
|
@__validation_state = State.new
|
|
105
|
-
self.class
|
|
134
|
+
if (block = self.class.validation_block)
|
|
135
|
+
instance_exec(&block)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def subject
|
|
140
|
+
@__validation_state.instance
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def attribute(name)
|
|
144
|
+
RuleChain.new(self, name, @__validation_state).attribute
|
|
106
145
|
end
|
|
107
146
|
|
|
108
147
|
def required(attribute, options)
|
data/lib/rubee.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Rubee
|
|
|
17
17
|
CSS_DIR = File.join(APP_ROOT, LIB, 'css') unless defined?(CSS_DIR)
|
|
18
18
|
ROOT_PATH = File.expand_path(File.join(__dir__, '..')) unless defined?(ROOT_PATH)
|
|
19
19
|
|
|
20
|
-
VERSION = '2.
|
|
20
|
+
VERSION = '2.2.0'
|
|
21
21
|
|
|
22
22
|
require_relative 'rubee/router'
|
|
23
23
|
require_relative 'rubee/logger'
|
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
require_relative '../test_helper'
|
|
2
2
|
|
|
3
|
+
class TestController < Rubee::BaseController
|
|
4
|
+
include(Rubee::AuthTokenable)
|
|
5
|
+
auth_methods(:show)
|
|
6
|
+
def show
|
|
7
|
+
response_with(type: :json, object: { ok: :ok })
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# POST /test/login (login logic)
|
|
11
|
+
def login
|
|
12
|
+
if authentificate! # AuthTokenable method that init @token_header
|
|
13
|
+
# Redirect to restricted area, make sure headers: @token_header is passed
|
|
14
|
+
response_with(type: :json, object: { ok: :ok }, headers: @token_header)
|
|
15
|
+
else
|
|
16
|
+
@error = "Wrong email or password"
|
|
17
|
+
response_with(type: :json, object: { error: 'user unauthenticated' }, status: :unauthenticated)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# POST /test/logout (logout logic)
|
|
22
|
+
def logout
|
|
23
|
+
unauthentificate! # AuthTokenable method aimed to handle logout action.
|
|
24
|
+
# Make sure @zeroed_token_header is paRssed within headers options
|
|
25
|
+
response_with(type: :json, object: { ok: 'logged out' }, headers: @zeroed_token_header)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class TesttwoController < Rubee::BaseController
|
|
30
|
+
include(Rubee::AuthTokenable)
|
|
31
|
+
auth_methods(:show)
|
|
32
|
+
def show
|
|
33
|
+
response_with(type: :json, object: { ok: :ok })
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# POST /testtwo/login (login logic)
|
|
37
|
+
def login
|
|
38
|
+
if authentificate!(user_model: Client, login: :name, password: :digest_password)
|
|
39
|
+
response_with(type: :json, object: { ok: :ok }, headers: @token_header)
|
|
40
|
+
else
|
|
41
|
+
@error = "Wrong email or password"
|
|
42
|
+
response_with(type: :json, object: { error: 'user unauthenticated' }, status: :unauthenticated)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# POST /testtwo/logout (logout logic)
|
|
47
|
+
def logout
|
|
48
|
+
unauthentificate!(user_model: Client, login: :name, password: :digest_password)
|
|
49
|
+
response_with(type: :json, object: { ok: 'logged out' }, headers: @zeroed_token_header)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
3
53
|
class AuthTokenableTest < Minitest::Test
|
|
4
54
|
include Rack::Test::Methods
|
|
5
55
|
|
|
@@ -9,21 +59,43 @@ class AuthTokenableTest < Minitest::Test
|
|
|
9
59
|
|
|
10
60
|
def setup
|
|
11
61
|
Rubee::Autoload.call
|
|
62
|
+
Rubee::Router.draw do |route|
|
|
63
|
+
route.post('/test/login', to: 'test#login')
|
|
64
|
+
route.post('/test/logout', to: 'test#logout')
|
|
65
|
+
route.get('/test/show', to: 'test#show')
|
|
66
|
+
route.post('/testtwo/login', to: 'testtwo#login')
|
|
67
|
+
route.post('/testtwo/logout', to: 'testtwo#logout')
|
|
68
|
+
route.get('/testtwo/show', to: 'testtwo#show')
|
|
69
|
+
end
|
|
70
|
+
User.create(email: '9oU8S@example.com', password: '123456')
|
|
71
|
+
Client.create(name: '9oU8S@example.com', digest_password: '123456')
|
|
12
72
|
end
|
|
13
73
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
return unless WelcomeController.instance_variable_defined?(:@auth_methods)
|
|
74
|
+
def test_test_controller_included_auth_tokenable
|
|
75
|
+
get('/test/show')
|
|
17
76
|
|
|
18
|
-
|
|
77
|
+
assert_equal(last_response.status, 401)
|
|
19
78
|
end
|
|
20
79
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
80
|
+
def test_test_controller_included_auth_tokenable_authenticated
|
|
81
|
+
post('/test/login', { email: '9oU8S@example.com', password: '123456' })
|
|
82
|
+
rack_mock_session.cookie_jar["jwt"] = last_response.cookies["jwt"].value.last
|
|
83
|
+
get('/test/show')
|
|
24
84
|
|
|
25
|
-
|
|
85
|
+
assert_equal(last_response.status, 200)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_test_controller_included_auth_tokenable_unauthenticated_custom_model
|
|
89
|
+
get('/testtwo/show')
|
|
26
90
|
|
|
27
91
|
assert_equal(last_response.status, 401)
|
|
28
92
|
end
|
|
93
|
+
|
|
94
|
+
def test_test_controller_included_auth_tokenable_authenticated_custom_model
|
|
95
|
+
post('/testtwo/login', { name: '9oU8S@example.com', digest_password: '123456' })
|
|
96
|
+
rack_mock_session.cookie_jar["jwt"] = last_response.cookies["jwt"].value.last
|
|
97
|
+
get('/testtwo/show')
|
|
98
|
+
|
|
99
|
+
assert_equal(last_response.status, 200)
|
|
100
|
+
end
|
|
29
101
|
end
|
|
@@ -18,9 +18,8 @@ describe 'Account model' do
|
|
|
18
18
|
|
|
19
19
|
describe '#validate_before_persist' do
|
|
20
20
|
it 'rasies error if account is not valid' do
|
|
21
|
-
Account.validate do
|
|
22
|
-
|
|
23
|
-
.required(:addres, required: "address is required")
|
|
21
|
+
Account.validate do
|
|
22
|
+
required(:addres, required: "address is required")
|
|
24
23
|
.type(String, type: "address must be string")
|
|
25
24
|
end
|
|
26
25
|
Account.validate_before_persist!
|
|
@@ -47,17 +47,17 @@ describe 'Comment model' do
|
|
|
47
47
|
|
|
48
48
|
describe 'validatable' do
|
|
49
49
|
def include_and_validate(required: true)
|
|
50
|
-
# Comment.include(Rubee::Validatable)
|
|
51
50
|
required_or_optional = required ? :required : :optional
|
|
52
|
-
required_or_optional_args = required ? [
|
|
53
|
-
Comment.validate do
|
|
54
|
-
|
|
51
|
+
required_or_optional_args = required ? [required: "text filed is required"] : []
|
|
52
|
+
Comment.validate do
|
|
53
|
+
attribute(:text).send(
|
|
55
54
|
required_or_optional, *required_or_optional_args
|
|
56
55
|
)
|
|
57
56
|
.type(String, type: "text field must be string")
|
|
58
|
-
.condition(proc {
|
|
57
|
+
.condition(proc { text.length > 4 }, { length: "text length must be greater than 4" })
|
|
59
58
|
end
|
|
60
59
|
end
|
|
60
|
+
|
|
61
61
|
it 'is valid' do
|
|
62
62
|
include_and_validate
|
|
63
63
|
comment = Comment.new(text: 'test it as valid')
|
|
@@ -180,5 +180,47 @@ describe 'Comment model' do
|
|
|
180
180
|
assert_equal('testerter', comment.text)
|
|
181
181
|
end
|
|
182
182
|
end
|
|
183
|
+
|
|
184
|
+
describe 'default errors as error' do
|
|
185
|
+
it 'assembles error hash' do
|
|
186
|
+
Comment.validate do
|
|
187
|
+
attribute(:text).required.type(String).condition(-> { text.length > 4 })
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
comment = Comment.new(text: 'test')
|
|
191
|
+
_(comment.valid?).must_equal(false)
|
|
192
|
+
_(comment.errors[:text]).must_equal({ message: "condition is not met" })
|
|
193
|
+
|
|
194
|
+
comment = Comment.new(text: 123)
|
|
195
|
+
_(comment.valid?).must_equal(false)
|
|
196
|
+
_(comment.errors[:text]).must_equal({ message: "attribute must be String" })
|
|
197
|
+
|
|
198
|
+
comment = Comment.new(user_id: User.last)
|
|
199
|
+
_(comment.valid?).must_equal(false)
|
|
200
|
+
_(comment.errors[:text]).must_equal({ message: "attribute 'text' is required" })
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
describe 'message instead hash as error' do
|
|
205
|
+
it 'assembles error hash' do
|
|
206
|
+
Comment.validate do
|
|
207
|
+
attribute(:text)
|
|
208
|
+
.required("Text is a mandatory field").type(String, "Text must be a string")
|
|
209
|
+
.condition(-> { text.length > 4 }, "Text length must be greater than 4")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
comment = Comment.new(text: 'test')
|
|
213
|
+
_(comment.valid?).must_equal(false)
|
|
214
|
+
_(comment.errors[:text]).must_equal({ message: "Text length must be greater than 4" })
|
|
215
|
+
|
|
216
|
+
comment = Comment.new(text: 123)
|
|
217
|
+
_(comment.valid?).must_equal(false)
|
|
218
|
+
_(comment.errors[:text]).must_equal({ message: "Text must be a string" })
|
|
219
|
+
|
|
220
|
+
comment = Comment.new(user_id: User.last)
|
|
221
|
+
_(comment.valid?).must_equal(false)
|
|
222
|
+
_(comment.errors[:text]).must_equal({ message: "Text is a mandatory field" })
|
|
223
|
+
end
|
|
224
|
+
end
|
|
183
225
|
end
|
|
184
226
|
end
|
data/lib/tests/test.db
CHANGED
|
Binary file
|
data/readme.md
CHANGED
|
@@ -151,14 +151,14 @@ cd my_project
|
|
|
151
151
|
|
|
152
152
|
***Prerequisites***<br />
|
|
153
153
|
Make sure:
|
|
154
|
-
**Ruby** language (3.1
|
|
154
|
+
**Ruby** language (3.1 or higher, 3.4.1 recommended) is installed
|
|
155
155
|
**Bundler** is installed
|
|
156
156
|
|
|
157
157
|
```bash
|
|
158
158
|
bundle install
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
4. Run
|
|
161
|
+
4. Run ru.Bee server. Default port is 7000
|
|
162
162
|
```bash
|
|
163
163
|
rubee start # or rubee start_dev for development
|
|
164
164
|
|
|
@@ -881,70 +881,88 @@ class Foo
|
|
|
881
881
|
@age = age
|
|
882
882
|
end
|
|
883
883
|
|
|
884
|
-
validate do
|
|
885
|
-
|
|
886
|
-
.required(:name, required: 'Name is required')
|
|
887
|
-
.type(String, type: 'must be a string')
|
|
888
|
-
.condition(->{ foo.name.length > 2 }, length: 'Name must be at least 3 characters long')
|
|
884
|
+
validate do
|
|
885
|
+
attribute(:name).required.type(String).condition(->{ name.length > 2 })
|
|
889
886
|
|
|
890
|
-
|
|
891
|
-
.required(
|
|
892
|
-
.type(Integer,
|
|
893
|
-
.condition(->{
|
|
887
|
+
attribute(:age)
|
|
888
|
+
.required('Age is a manadatory field')
|
|
889
|
+
.type(Integer, error_message: 'Must be an integerRRRRRRrrr!')
|
|
890
|
+
.condition(->{ age > 18 }, fancy_error: 'You must be at least 18 years old, dude!')
|
|
894
891
|
end
|
|
895
892
|
end
|
|
896
893
|
```
|
|
894
|
+
Then we can evaluate it in the ru.Bee console
|
|
897
895
|
```bash
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
896
|
+
=> #<Proc:0x000000010d389d80 (irb):32>
|
|
897
|
+
irb(main):041> Foo.new("Test", 20)
|
|
898
|
+
=> #<Foo:0x000000010d383fc0 @__validation_state=#<Rubee::Validatable::State:0x000000010d383de0 @errors={}, @valid=true>, @age=20, @name="Test">
|
|
899
|
+
irb(main):042> Foo.new("Test", 1)
|
|
900
|
+
=>
|
|
901
|
+
#<Foo:0x000000010ce61c40
|
|
902
|
+
@__validation_state=#<Rubee::Validatable::State:0x000000010ce61bc8 @errors={age: {fancy_error: "You must be at least 18 years old, dude!"}}, @valid=false>,
|
|
903
|
+
@age=1,
|
|
904
|
+
@name="Test">
|
|
905
|
+
irb(main):043> Foo.new("Test", nil)
|
|
906
|
+
=>
|
|
907
|
+
#<Foo:0x000000010c46f200
|
|
908
|
+
@__validation_state=#<Rubee::Validatable::State:0x000000010c46f070 @errors={age: {message: "Age is a manadatory field"}}, @valid=false>,
|
|
909
|
+
@age=nil,
|
|
910
|
+
@name="Test">
|
|
911
|
+
irb(main):044> Foo.new("Te", 20)
|
|
912
|
+
=>
|
|
913
|
+
#<Foo:0x000000010cfe9270
|
|
914
|
+
@__validation_state=#<Rubee::Validatable::State:0x000000010cfe91f8 @errors={name: {message: "condition is not met"}}, @valid=false>,
|
|
915
|
+
@age=20,
|
|
916
|
+
@name="Te">
|
|
917
|
+
irb(main):045> foo = Foo.new("Joe", "wrong")
|
|
918
|
+
=>
|
|
919
|
+
#<Foo:0x000000010d32eb38
|
|
920
|
+
...
|
|
921
|
+
irb(main):046> foo.valid?
|
|
922
|
+
=> false
|
|
923
|
+
irb(main):047> foo.errors
|
|
924
|
+
=> {age: {error_message: "Must be an integerRRRRRRrrr!"}}
|
|
916
925
|
```
|
|
917
926
|
Model example
|
|
918
927
|
```ruby
|
|
919
928
|
class User < Rubee::SequelObject
|
|
920
929
|
attr_accessor :id, :email, :password, :created, :updated
|
|
921
930
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
validate_before_persist! # This will validate and raise error in case invalid before saving to DB
|
|
931
|
+
validate_before_persist! # This will validate and raise error in case invalid before saving to DB
|
|
932
|
+
validate do
|
|
933
|
+
attribute(:email).required
|
|
934
|
+
.condition(
|
|
935
|
+
->{ email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i) }, error: 'Wrong email format'
|
|
936
|
+
)
|
|
937
|
+
end
|
|
930
938
|
end
|
|
931
939
|
```
|
|
932
940
|
```bash
|
|
933
|
-
irb(main):
|
|
934
|
-
=>
|
|
935
|
-
|
|
941
|
+
irb(main):074> user = User.new(email: "wrong", password: 123)
|
|
942
|
+
=>
|
|
943
|
+
#<User:0x000000010d2c3e78
|
|
944
|
+
...
|
|
945
|
+
irb(main):075> user.valid?
|
|
936
946
|
=> false
|
|
937
|
-
irb(main):
|
|
938
|
-
=> {email: {
|
|
939
|
-
irb(main):
|
|
940
|
-
|
|
941
|
-
irb(main):
|
|
947
|
+
irb(main):076> user.errors
|
|
948
|
+
=> {email: {error: "Wrong email format"}}
|
|
949
|
+
irb(main):077> user.save
|
|
950
|
+
=>{email: {error: "Wrong email format"}} (Rubee::Validatable::Error) ..
|
|
951
|
+
irb(main):078> user.email = "ok@ok.com"
|
|
942
952
|
=> "ok@ok.com"
|
|
943
|
-
irb(main):
|
|
953
|
+
irb(main):079> user.valid?
|
|
944
954
|
=> true
|
|
945
|
-
irb(main):
|
|
955
|
+
irb(main):080> user.save
|
|
946
956
|
=> true
|
|
947
|
-
|
|
957
|
+
irb(main):081> user
|
|
958
|
+
=>
|
|
959
|
+
#<User:0x000000010d2c3e78
|
|
960
|
+
@__validation_state=#<Rubee::Validatable::State:0x000000010cb28628 @errors={}, @valid=true>,
|
|
961
|
+
@created=2025-11-30 17:18:52.254197 -0500,
|
|
962
|
+
@email="ok@ok.com",
|
|
963
|
+
@id=2260,
|
|
964
|
+
@password=123,
|
|
965
|
+
@updated=2025-11-30 17:18:52.254206 -0500>
|
|
948
966
|
```
|
|
949
967
|
[Back to content](#content)
|
|
950
968
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ru.Bee
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleg Saltykov
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- lib/css/app.css
|
|
59
59
|
- lib/db/create_accounts.rb
|
|
60
60
|
- lib/db/create_addresses.rb
|
|
61
|
+
- lib/db/create_clients.rb
|
|
61
62
|
- lib/db/create_comments.rb
|
|
62
63
|
- lib/db/create_posts.rb
|
|
63
64
|
- lib/db/create_users.rb
|
|
@@ -280,6 +281,7 @@ files:
|
|
|
280
281
|
- lib/tests/controllers/users_controller_test.rb
|
|
281
282
|
- lib/tests/example_models/account.rb
|
|
282
283
|
- lib/tests/example_models/address.rb
|
|
284
|
+
- lib/tests/example_models/client.rb
|
|
283
285
|
- lib/tests/example_models/comment.rb
|
|
284
286
|
- lib/tests/example_models/post.rb
|
|
285
287
|
- lib/tests/example_models/user.rb
|