omniauth-identity 3.0.1 → 3.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/LICENSE +2 -1
- data/README.md +81 -42
- data/lib/omniauth-identity.rb +2 -0
- data/lib/omniauth-identity/version.rb +3 -1
- data/lib/omniauth/identity.rb +4 -0
- data/lib/omniauth/identity/model.rb +85 -29
- data/lib/omniauth/identity/models/active_record.rb +2 -0
- data/lib/omniauth/identity/models/couch_potato.rb +3 -0
- data/lib/omniauth/identity/models/mongoid.rb +3 -0
- data/lib/omniauth/identity/models/no_brainer.rb +31 -0
- data/lib/omniauth/identity/models/sequel.rb +48 -0
- data/lib/omniauth/identity/secure_password.rb +2 -0
- data/lib/omniauth/strategies/identity.rb +105 -32
- data/spec/omniauth/identity/model_spec.rb +2 -0
- data/spec/omniauth/identity/models/active_record_spec.rb +3 -1
- data/spec/omniauth/identity/models/couch_potato_spec.rb +10 -6
- data/spec/omniauth/identity/models/mongoid_spec.rb +11 -7
- data/spec/omniauth/identity/models/no_brainer_spec.rb +17 -0
- data/spec/omniauth/identity/models/sequel_spec.rb +23 -0
- data/spec/omniauth/identity/secure_password_spec.rb +5 -3
- data/spec/omniauth/strategies/identity_spec.rb +128 -15
- data/spec/spec_helper.rb +1 -0
- metadata +29 -129
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 469e5989686f13b8e924e7efc5cfbd2f205a13d45da6410f454f2d0b14e884ea
|
4
|
+
data.tar.gz: 38424bbbc877d8d18aa4a35916697c59b31379809e05e5ff620de0089f53759c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1458cc73c99ca0bb267f9730d5e64daa8d6b7460720dd5790d443292cc8e938434190fcaf55195e5f039ffd50286018045ac2e0ed8c9a8e83eb5cae5878dcb62
|
7
|
+
data.tar.gz: 439a1bd2702a7d5a729be16ad65fdd079a6a867f9ffa26c823b63d675dcaf2127311097b4f51a8c42425c33aba1d490dc46abf92d4bdab6c0185e191952d244c
|
data/CHANGELOG.md
CHANGED
@@ -6,9 +6,61 @@ All notable changes to this project will be documented in this file.
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
8
8
|
|
9
|
+
## [Unreleased]
|
10
|
+
|
11
|
+
## [3.0.5] - 2021-03-19
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
- Fix breaking changes introduced by [#86's](https://github.com/omniauth/omniauth-identity/pull/86) introduction of `:on_validation`
|
16
|
+
|
17
|
+
### Added
|
18
|
+
|
19
|
+
- Define `#save`, `#persisted?` and `::create` on `Omniauth::Identity::Model`
|
20
|
+
- Add `@since` YARD tags to interface methods
|
21
|
+
- Refactor `Omniauth::Strategies::Identity.registration_phase` to support `Omniauth::Identity::Model`-inheriting classes that do not define `#save`.
|
22
|
+
- This support will be dropped in v4.0.
|
23
|
+
|
24
|
+
## [3.0.4] - 2021-02-14
|
25
|
+
|
26
|
+
### Added
|
27
|
+
|
28
|
+
- Add support for [sequel ORM](http://sequel.jeremyevans.net/)
|
29
|
+
|
30
|
+
## [3.0.3] - 2021-02-14
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
- Add option `:on_validation`, which can be used to add a Captcha
|
35
|
+
- See [example here](https://github.com/omniauth/omniauth-identity/pull/86#issue-63225122)
|
36
|
+
- Add support for nobrainer, an ORM for RethinkDB
|
37
|
+
- Validation error message on invalid registration form submission
|
38
|
+
|
39
|
+
### Removed
|
40
|
+
|
41
|
+
- ruby-head build... simply too slow
|
42
|
+
|
43
|
+
## [3.0.2] - 2021-02-14
|
44
|
+
|
45
|
+
### Fixed
|
46
|
+
|
47
|
+
- Github Actions CI Build for Ruby 2.4, 3.0 and ruby-head
|
48
|
+
- Updated copyright
|
49
|
+
- Code style cleanup
|
50
|
+
- Added Code Climate "Quality"
|
51
|
+
- Updated Readme
|
52
|
+
|
53
|
+
## [3.0.1] - 2021-02-14
|
54
|
+
|
55
|
+
### Fixed
|
56
|
+
|
57
|
+
- Github Actions CI Build for various Rubies
|
58
|
+
|
9
59
|
## [3.0] - 2021-02-13
|
10
60
|
|
11
61
|
### Added
|
62
|
+
|
63
|
+
- Compatibility with Ruby 3
|
12
64
|
- Add option `:enable_login` to bypass OmniAuth disabling of GET method (default `true`)
|
13
65
|
- NOTE: This restores compatibility between this gem and the current, core, omniauth gem!
|
14
66
|
- README updates, including a rename to README.md
|
@@ -20,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
20
72
|
- Documentation in README.md
|
21
73
|
|
22
74
|
### Removed
|
75
|
+
|
23
76
|
- Support for Rubies < 2.4
|
24
77
|
- Support for DataMapper, which died long ago.
|
25
78
|
- Unwanted git artifacts
|
@@ -27,10 +80,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
27
80
|
## [2.0] - 2020-09-01
|
28
81
|
|
29
82
|
### Added
|
83
|
+
|
30
84
|
- CHANGELOG to maintain a history of changes.
|
31
85
|
- Include mongoid-rspec gem.
|
32
86
|
|
33
87
|
### Changed
|
88
|
+
|
34
89
|
- Fix failing Specs
|
35
90
|
- Update Spec syntax to RSpec 3
|
36
91
|
- Fix deprecation Warnings
|
data/LICENSE
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2021 OmniAuth-Identity Maintainers
|
2
|
+
Copyright (c) 2020 Peter Boling, Andrew Roberts, and Jellybooks Ltd.
|
2
3
|
Copyright (c) 2010-2015 Michael Bleigh and Intridea, Inc.
|
3
4
|
|
4
5
|
Permission is hereby granted, free of charge, to any person obtaining
|
data/README.md
CHANGED
@@ -1,16 +1,26 @@
|
|
1
1
|
# OmniAuth Identity
|
2
2
|
|
3
|
+
[![Version](https://img.shields.io/gem/v/omniauth-identity.svg)](https://rubygems.org/gems/omniauth-identity)
|
4
|
+
[![Depfu](https://badges.depfu.com/badges/6c9b45362951b872127f9e46d39bed76/count.svg)](https://depfu.com/github/omniauth/omniauth-identity?project_id=22381)
|
5
|
+
[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fomniauth%2Fomniauth-identity%2Fbadge&style=flat)](https://actions-badge.atrox.dev/omniauth/omniauth-identity/goto)
|
6
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/621d6211cb2e0959ce00/maintainability)](https://codeclimate.com/github/omniauth/omniauth-identity/maintainability)
|
7
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/621d6211cb2e0959ce00/test_coverage)](https://codeclimate.com/github/omniauth/omniauth-identity/test_coverage)
|
8
|
+
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
|
9
|
+
[![Open Source Helpers](https://www.codetriage.com/omniauth/omniauth-identity/badges/users.svg)](https://www.codetriage.com/omniauth/omniauth-identity)
|
10
|
+
[![Downloads Rank](https://img.shields.io/gem/rd/omniauth-identity.svg)](https://rubygems.org/gems/omniauth-identity)
|
11
|
+
|
3
12
|
The OmniAuth Identity gem provides a way for applications to utilize a
|
4
|
-
traditional
|
13
|
+
traditional username/password based authentication system without the need
|
5
14
|
to give up the simple authentication flow provided by OmniAuth. Identity
|
6
15
|
is designed on purpose to be as featureless as possible: it provides the
|
7
16
|
basic construct for user management and then gets out of the way.
|
8
17
|
|
9
18
|
## Compatibility
|
10
19
|
|
11
|
-
This gem is compatible with, as of Feb 2021:
|
12
|
-
|
13
|
-
|
20
|
+
This gem is compatible with, as of Feb 2021, version 3:
|
21
|
+
|
22
|
+
* Latest released version of omniauth, v2.0.2
|
23
|
+
* Ruby 2.4, 2.5, 2.6, 2.7, 3.0, ruby-head
|
14
24
|
|
15
25
|
## Installation
|
16
26
|
|
@@ -48,6 +58,13 @@ with `:model` argument above) that will be able to persist the information
|
|
48
58
|
provided by the user. Luckily for you, there are pre-built models for popular
|
49
59
|
ORMs that make this dead simple.
|
50
60
|
|
61
|
+
Once you've got an `Identity` persistence model and the strategy up and
|
62
|
+
running, you can point users to `/auth/identity` and it will request
|
63
|
+
that they log in or give them the opportunity to sign up for an account.
|
64
|
+
Once they have authenticated with their identity, OmniAuth will call
|
65
|
+
through to `/auth/identity/callback` with the same kinds of information
|
66
|
+
it would had the user authenticated through an external provider.
|
67
|
+
|
51
68
|
**Note:** OmniAuth Identity is different from many other user authentication
|
52
69
|
systems in that it is *not* built to store authentication information in your primary
|
53
70
|
`User` model. Instead, the `Identity` model should be **associated** with your
|
@@ -68,52 +85,42 @@ class Identity < OmniAuth::Identity::Models::ActiveRecord
|
|
68
85
|
end
|
69
86
|
```
|
70
87
|
|
71
|
-
###
|
88
|
+
### Sequel
|
72
89
|
|
73
|
-
|
74
|
-
fields that you will need.
|
90
|
+
[Sequel](http://sequel.jeremyevans.net/) is an alternative to ActiveRecord.
|
75
91
|
|
76
|
-
|
77
|
-
|
78
|
-
include Mongoid::Document
|
79
|
-
include OmniAuth::Identity::Models::Mongoid
|
92
|
+
Just include `OmniAuth::Identity::Models::Sequel` mixin, and specify
|
93
|
+
whatever else you will need.
|
80
94
|
|
81
|
-
|
82
|
-
|
83
|
-
|
95
|
+
```ruby
|
96
|
+
class SequelTestIdentity < Sequel::Model
|
97
|
+
include OmniAuth::Identity::Models::Sequel
|
98
|
+
auth_key :email
|
99
|
+
# whatever else you want!
|
84
100
|
end
|
85
101
|
```
|
86
102
|
|
87
|
-
### MongoMapper
|
88
|
-
|
89
|
-
Unfortunately MongoMapper is **not supported** in `omniauth-identity` from >= v2.0 as a result of it
|
90
|
-
not being maintained for several years.
|
91
103
|
|
92
|
-
|
93
|
-
requirements. Therefore precedence was given to Mongoid as it is significantly more
|
94
|
-
popular and actively maintained.
|
95
|
-
|
96
|
-
### DataMapper
|
104
|
+
### Mongoid
|
97
105
|
|
98
|
-
Include the `OmniAuth::Identity::Models::
|
106
|
+
Include the `OmniAuth::Identity::Models::Mongoid` mixin and specify
|
99
107
|
fields that you will need.
|
100
108
|
|
101
109
|
```ruby
|
102
110
|
class Identity
|
103
|
-
include
|
104
|
-
include OmniAuth::Identity::Models::
|
105
|
-
|
106
|
-
property :id, Serial
|
107
|
-
property :email, String
|
108
|
-
property :password_digest, Text
|
111
|
+
include Mongoid::Document
|
112
|
+
include OmniAuth::Identity::Models::Mongoid
|
109
113
|
|
110
|
-
|
114
|
+
field :email, type: String
|
115
|
+
field :name, type: String
|
116
|
+
field :password_digest, type: String
|
111
117
|
end
|
112
118
|
```
|
113
119
|
|
114
120
|
### CouchPotato
|
115
121
|
|
116
|
-
Include the `OmniAuth::Identity::Models::CouchPotatoModule` mixin and specify
|
122
|
+
Include the `OmniAuth::Identity::Models::CouchPotatoModule` mixin and specify
|
123
|
+
fields that you will need.
|
117
124
|
|
118
125
|
```ruby
|
119
126
|
class Identity
|
@@ -131,13 +138,25 @@ class Identity
|
|
131
138
|
end
|
132
139
|
```
|
133
140
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
+
### NoBrainer
|
142
|
+
|
143
|
+
[NoBrainer](http://nobrainer.io/) is an ORM for [RethinkDB](https://rethinkdb.com/).
|
144
|
+
|
145
|
+
Include the `OmniAuth::Identity::Models::NoBrainer` mixin and specify
|
146
|
+
fields that you will need.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Identity
|
150
|
+
include NoBrainer::Document
|
151
|
+
include OmniAuth::Identity::Models::NoBrainer
|
152
|
+
|
153
|
+
auth_key :email
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
### Ruby Object Mapper
|
158
|
+
|
159
|
+
Would love to add a mixin for the [Ruby Object Mapper (ROM)](https://rom-rb.org/) if anyone wants to work on it!
|
141
160
|
|
142
161
|
## Custom Auth Model
|
143
162
|
|
@@ -226,12 +245,32 @@ Note: Be careful when customizing `locate_conditions`. The best way to modify t
|
|
226
245
|
to copy the default value, and then add to the hash. Removing the default condition will almost
|
227
246
|
always break things!
|
228
247
|
|
248
|
+
## Customizing Other Things
|
249
|
+
|
250
|
+
From the code - here are the options we have for you, a couple of which are documented above, and the rest are documented... in the specs we hope!?
|
251
|
+
```
|
252
|
+
option :fields, %i[name email]
|
253
|
+
|
254
|
+
# Primary Feature Switches:
|
255
|
+
option :enable_registration, true # See #other_phase and #request_phase
|
256
|
+
option :enable_login, true # See #other_phase
|
257
|
+
|
258
|
+
# Customization Options:
|
259
|
+
option :on_login, nil # See #request_phase
|
260
|
+
option :on_validation, nil # See #registration_phase
|
261
|
+
option :on_registration, nil # See #registration_phase
|
262
|
+
option :on_failed_registration, nil # See #registration_phase
|
263
|
+
option :locate_conditions, ->(req) { { model.auth_key => req['auth_key'] } }
|
264
|
+
```
|
265
|
+
|
266
|
+
Please contribute some documentation if you have the gumption! The maintainer's time is limited, and sometimes the authors of PRs with new options don't update the _this_ readme. 😭
|
267
|
+
|
229
268
|
## License
|
230
269
|
|
231
270
|
MIT License. See LICENSE for details.
|
232
271
|
|
233
272
|
## Copyright
|
234
273
|
|
235
|
-
Copyright (c) 2021 OmniAuth-Identity Maintainers
|
236
|
-
Copyright (c) 2020 Peter Boling, Andrew Roberts, and Jellybooks Ltd.
|
237
|
-
Copyright (c) 2010-2015 Michael Bleigh, and Intridea, Inc.
|
274
|
+
* Copyright (c) 2021 OmniAuth-Identity Maintainers
|
275
|
+
* Copyright (c) 2020 Peter Boling, Andrew Roberts, and Jellybooks Ltd.
|
276
|
+
* Copyright (c) 2010-2015 Michael Bleigh, and Intridea, Inc.
|
data/lib/omniauth-identity.rb
CHANGED
data/lib/omniauth/identity.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'omniauth'
|
2
4
|
|
3
5
|
module OmniAuth
|
@@ -12,6 +14,8 @@ module OmniAuth
|
|
12
14
|
autoload :ActiveRecord, 'omniauth/identity/models/active_record'
|
13
15
|
autoload :Mongoid, 'omniauth/identity/models/mongoid'
|
14
16
|
autoload :CouchPotatoModule, 'omniauth/identity/models/couch_potato'
|
17
|
+
autoload :NoBrainer, 'omniauth/identity/models/no_brainer'
|
18
|
+
autoload :Sequel, 'omniauth/identity/models/sequel'
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
@@ -1,24 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OmniAuth
|
2
4
|
module Identity
|
3
5
|
# This module provides an includable interface for implementing the
|
4
6
|
# necessary API for OmniAuth Identity to properly locate identities
|
5
|
-
# and provide all necessary information.
|
6
|
-
#
|
7
|
-
#
|
7
|
+
# and provide all necessary information.
|
8
|
+
#
|
9
|
+
# All methods marked as abstract must be implemented in the
|
10
|
+
# including class for things to work properly.
|
11
|
+
#
|
12
|
+
### Singleton API
|
13
|
+
#
|
14
|
+
# * locate(key)
|
15
|
+
# * create(*args) - Deprecated in v3.0.5; Will be removed in v4.0
|
16
|
+
#
|
17
|
+
### Instance API
|
18
|
+
#
|
19
|
+
# * save
|
20
|
+
# * persisted?
|
21
|
+
# * authenticate(password)
|
22
|
+
#
|
8
23
|
module Model
|
24
|
+
SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze
|
25
|
+
|
9
26
|
def self.included(base)
|
10
27
|
base.extend ClassMethods
|
11
28
|
end
|
12
29
|
|
13
30
|
module ClassMethods
|
14
|
-
|
15
|
-
#
|
16
|
-
# @abstract
|
17
|
-
# @param [String] key The unique login key.
|
18
|
-
# @return [Model] An instance of the identity model class.
|
19
|
-
def locate(key)
|
20
|
-
raise NotImplementedError
|
21
|
-
end
|
31
|
+
extend Gem::Deprecate
|
22
32
|
|
23
33
|
# Authenticate a user with the given key and password.
|
24
34
|
#
|
@@ -37,10 +47,57 @@ module OmniAuth
|
|
37
47
|
# @return [String] The method name.
|
38
48
|
def auth_key(method = false)
|
39
49
|
@auth_key = method.to_s unless method == false
|
40
|
-
@auth_key = nil if @auth_key == ''
|
50
|
+
@auth_key = nil if !defined?(@auth_key) || @auth_key == ''
|
41
51
|
|
42
52
|
@auth_key || 'email'
|
43
53
|
end
|
54
|
+
|
55
|
+
# Persists a new Identity object to the ORM.
|
56
|
+
# Defaults to calling super. Override as needed per ORM.
|
57
|
+
#
|
58
|
+
# @deprecated v4.0 will begin using {#new} with {#save} instead.
|
59
|
+
# @abstract
|
60
|
+
# @param [Hash] args Attributes of the new instance.
|
61
|
+
# @return [Model] An instance of the identity model class.
|
62
|
+
# @since 3.0.5
|
63
|
+
def create(*args)
|
64
|
+
raise NotImplementedError unless defined?(super)
|
65
|
+
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
# Locate an identity given its unique login key.
|
70
|
+
#
|
71
|
+
# @abstract
|
72
|
+
# @param [String] key The unique login key.
|
73
|
+
# @return [Model] An instance of the identity model class.
|
74
|
+
def locate(key)
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Persists a new Identity object to the ORM.
|
80
|
+
# Default raises an error. Override as needed per ORM.
|
81
|
+
#
|
82
|
+
# @abstract
|
83
|
+
# @return [Model] An instance of the identity model class.
|
84
|
+
# @since 3.0.5
|
85
|
+
def save
|
86
|
+
raise NotImplementedError unless defined?(super)
|
87
|
+
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
# Checks if the Identity object is persisted in the ORM.
|
92
|
+
# Defaults to calling super. Override as needed per ORM.
|
93
|
+
#
|
94
|
+
# @abstract
|
95
|
+
# @return [true or false] true if object exists, false if not.
|
96
|
+
# @since 3.0.5
|
97
|
+
def persisted?
|
98
|
+
raise NotImplementedError unless defined?(super)
|
99
|
+
|
100
|
+
super
|
44
101
|
end
|
45
102
|
|
46
103
|
# Returns self if the provided password is correct, false
|
@@ -53,22 +110,6 @@ module OmniAuth
|
|
53
110
|
raise NotImplementedError
|
54
111
|
end
|
55
112
|
|
56
|
-
SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone]
|
57
|
-
# A hash of as much of the standard OmniAuth schema as is stored
|
58
|
-
# in this particular model. By default, this will call instance
|
59
|
-
# methods for each of the attributes it needs in turn, ignoring
|
60
|
-
# any for which `#respond_to?` is `false`.
|
61
|
-
#
|
62
|
-
# If `first_name`, `nickname`, and/or `last_name` is provided but
|
63
|
-
# `name` is not, it will be automatically calculated.
|
64
|
-
#
|
65
|
-
# @return [Hash] A string-keyed hash of user information.
|
66
|
-
def info
|
67
|
-
SCHEMA_ATTRIBUTES.each_with_object({}) do |attribute, hash|
|
68
|
-
hash[attribute] = send(attribute) if respond_to?(attribute)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
113
|
# An identifying string that must be globally unique to the
|
73
114
|
# application. Defaults to stringifying the `id` method.
|
74
115
|
#
|
@@ -104,13 +145,28 @@ module OmniAuth
|
|
104
145
|
# @param [String] value The value to which the auth key should be
|
105
146
|
# set.
|
106
147
|
def auth_key=(value)
|
107
|
-
auth_key_setter =
|
148
|
+
auth_key_setter = "#{self.class.auth_key}=".to_sym
|
108
149
|
if respond_to?(auth_key_setter)
|
109
150
|
send(auth_key_setter, value)
|
110
151
|
else
|
111
152
|
raise NotImplementedError
|
112
153
|
end
|
113
154
|
end
|
155
|
+
|
156
|
+
# A hash of as much of the standard OmniAuth schema as is stored
|
157
|
+
# in this particular model. By default, this will call instance
|
158
|
+
# methods for each of the attributes it needs in turn, ignoring
|
159
|
+
# any for which `#respond_to?` is `false`.
|
160
|
+
#
|
161
|
+
# If `first_name`, `nickname`, and/or `last_name` is provided but
|
162
|
+
# `name` is not, it will be automatically calculated.
|
163
|
+
#
|
164
|
+
# @return [Hash] A string-keyed hash of user information.
|
165
|
+
def info
|
166
|
+
SCHEMA_ATTRIBUTES.each_with_object({}) do |attribute, hash|
|
167
|
+
hash[attribute] = send(attribute) if respond_to?(attribute)
|
168
|
+
end
|
169
|
+
end
|
114
170
|
end
|
115
171
|
end
|
116
172
|
end
|