name_of_person 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +38 -0
- data/MIT-LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +11 -0
- data/lib/name_of_person.rb +5 -0
- data/lib/name_of_person/assignable_name.rb +16 -0
- data/lib/name_of_person/has_person_name.rb +14 -0
- data/lib/name_of_person/loaders/active_model_has_person_name.rb +9 -0
- data/lib/name_of_person/loaders/active_record_has_person_name.rb +8 -0
- data/lib/name_of_person/person_name.rb +58 -0
- data/name_of_person.gemspec +19 -0
- data/test/assignable_name_test.rb +25 -0
- data/test/has_person_name_test.rb +28 -0
- data/test/person_name_test.rb +104 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b03a84a4207dc1c212ede9e467ece80d14b4d73b889ad93c9665e177c1a744c0
|
4
|
+
data.tar.gz: 8e8420b6460848be39581db10a64b8b2a7dfa4504ad5b355cfae98cfda5366d8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f0be6604bb1a94775c009cbba1bb34d6369747cdd9466b35ae24385da15bbbcf4f00605913456b9360464dc26ffd8779183c1506567dc8a9eb3ee7becf28060
|
7
|
+
data.tar.gz: e31dc4fb63ae0dc3387d343842b6e5aae1348863fc6b6c82ce8770247157ef95ff47bed6dfca50c6d6d32d7f80666f7014108560fc687c09d063451002de36e6
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
name_of_person (0.1.0)
|
5
|
+
activesupport (>= 5.2.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (5.2.1)
|
11
|
+
activesupport (= 5.2.1)
|
12
|
+
activesupport (5.2.1)
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
+
i18n (>= 0.7, < 2)
|
15
|
+
minitest (~> 5.1)
|
16
|
+
tzinfo (~> 1.1)
|
17
|
+
byebug (10.0.2)
|
18
|
+
concurrent-ruby (1.0.5)
|
19
|
+
i18n (1.1.0)
|
20
|
+
concurrent-ruby (~> 1.0)
|
21
|
+
minitest (5.11.3)
|
22
|
+
rake (12.3.1)
|
23
|
+
thread_safe (0.3.6)
|
24
|
+
tzinfo (1.2.5)
|
25
|
+
thread_safe (~> 0.1)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
activemodel (>= 5.2.0)
|
32
|
+
bundler (~> 1.15)
|
33
|
+
byebug
|
34
|
+
name_of_person!
|
35
|
+
rake
|
36
|
+
|
37
|
+
BUNDLED WITH
|
38
|
+
1.16.3
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2018 Basecamp
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Name of Person
|
2
|
+
|
3
|
+
Presenting names for English-language applications where a basic model of first and last name(s) combined is sufficient. This approach is not meant to cover all possible naming cases, deal with other languages, or even titulations. Just the basics.
|
4
|
+
|
5
|
+
## Examples
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
# Relies on Person having a schema with first_name and last_name columns.
|
9
|
+
class Person < ApplicationRecord
|
10
|
+
has_person_name
|
11
|
+
end
|
12
|
+
|
13
|
+
# Saves a new record using { first_name: "David", last_name: "Heinemeier Hansson" }
|
14
|
+
person = Person.create! name: "David Heinemeier Hansson"
|
15
|
+
|
16
|
+
person.name.full # => "David Heinemeier Hansson"
|
17
|
+
person.name.first # => "David"
|
18
|
+
person.name.last # => "Heinemeier Hansson"
|
19
|
+
person.name.initials # => "DHH"
|
20
|
+
person.name.familiar # => "David H."
|
21
|
+
person.name.abbreviated # => "D. Heinemeier Hansson"
|
22
|
+
person.name.sorted # => "Heinemeier Hansson, David"
|
23
|
+
person.name.mentionable # => "davidh"
|
24
|
+
person.name.possessive # => "David Heinemeier Hansson's"
|
25
|
+
|
26
|
+
# Use directly
|
27
|
+
name = NameOfPerson::PersonName.full("David Heinemeier Hansson")
|
28
|
+
name.first # => "David"
|
29
|
+
```
|
30
|
+
|
31
|
+
## Maintenance Expectations
|
32
|
+
|
33
|
+
This library is an extraction from Basecamp that's been sufficient in almost unaltered form for over 10 years. While contributions are always welcome, do not expect a lot of feature evolution beyond the basics. Feel free to fork this library if you'd like to add large upgrades like titulations or different accomodations for other languages.
|
34
|
+
|
35
|
+
## License
|
36
|
+
|
37
|
+
Name of Person is released under the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'name_of_person/person_name'
|
2
|
+
|
3
|
+
module NameOfPerson
|
4
|
+
module AssignableName
|
5
|
+
# Assigns first_name and last_name attributes as extracted from a supplied full name.
|
6
|
+
def name=(name)
|
7
|
+
full_name = NameOfPerson::PersonName.full(name)
|
8
|
+
self.first_name, self.last_name = full_name.try(:first), full_name.try(:last)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns a PersonName object created from the first_name and last_name attributes.
|
12
|
+
def name
|
13
|
+
NameOfPerson::PersonName.new(first_name, last_name) if first_name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'name_of_person/assignable_name'
|
2
|
+
|
3
|
+
module NameOfPerson
|
4
|
+
module HasPersonName
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
# Defines the instance methods name/name= from AssignableName.
|
9
|
+
def has_person_name
|
10
|
+
include NameOfPerson::AssignableName
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
|
3
|
+
module NameOfPerson
|
4
|
+
class PersonName < String
|
5
|
+
attr_reader :first, :last
|
6
|
+
|
7
|
+
def self.full(full_name)
|
8
|
+
first, last = full_name.to_s.strip.split(/\s+/, 2)
|
9
|
+
new(first, last) if first.present?
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(first, last = nil)
|
13
|
+
raise ArgumentError, "First name is required" unless first.present?
|
14
|
+
@first, @last = first, last
|
15
|
+
super full
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns first + last, such as "Jason Fried".
|
19
|
+
def full
|
20
|
+
@full ||= last.present? ? "#{first} #{last}" : first
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns first + last initial, such as "Jason F.".
|
24
|
+
def familiar
|
25
|
+
@familiar ||= last.present? ? "#{first} #{last.first}." : first
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns first initial + last, such as "J. Fried".
|
29
|
+
def abbreviated
|
30
|
+
@abbreviated ||= last.present? ? "#{first.first}. #{last}" : first
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns last + first for sorting.
|
34
|
+
def sorted
|
35
|
+
@sorted ||= last.present? ? "#{last}, #{first}" : first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns full name with with trailing 's or ' if name ends in s.
|
39
|
+
def possessive
|
40
|
+
@possessive ||= "#{self}'#{"s" unless end_with?("s")}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns just the initials.
|
44
|
+
def initials
|
45
|
+
@initials ||= scan(/\b(\S)\S*/).join
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a mentionable version of the familiar name
|
49
|
+
def mentionable
|
50
|
+
@mentionable ||= familiar.chop.delete(' ').downcase
|
51
|
+
end
|
52
|
+
|
53
|
+
# Override to_yaml to serialize as a plain string.
|
54
|
+
def encode_with(coder)
|
55
|
+
coder.represent_scalar nil, to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'name_of_person'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.authors = 'David Heinemeier Hansson'
|
5
|
+
s.email = 'david@basecamp.com'
|
6
|
+
s.summary = 'Presenting names of people in full, familiar, abbreviated, and initialized forms (but without titulation etc)'
|
7
|
+
s.homepage = 'https://github.com/basecamp/name_of_person'
|
8
|
+
s.license = 'MIT'
|
9
|
+
|
10
|
+
s.required_ruby_version = '>= 2.4.0'
|
11
|
+
|
12
|
+
s.add_dependency 'activesupport', '>= 5.2.0'
|
13
|
+
|
14
|
+
s.add_development_dependency 'activemodel', '>= 5.2.0'
|
15
|
+
s.add_development_dependency 'bundler', '~> 1.15'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/testing/autorun'
|
3
|
+
|
4
|
+
require 'name_of_person/assignable_name'
|
5
|
+
|
6
|
+
class StructPerson < Struct.new(:first_name, :last_name)
|
7
|
+
include NameOfPerson::AssignableName
|
8
|
+
end
|
9
|
+
|
10
|
+
class AssignableNameTest < ActiveSupport::TestCase
|
11
|
+
include NameOfPerson
|
12
|
+
|
13
|
+
setup do
|
14
|
+
@person = StructPerson.new("David", "Heinemeier Hansson")
|
15
|
+
end
|
16
|
+
|
17
|
+
test "reading name" do
|
18
|
+
assert_equal PersonName.new("David", "Heinemeier Hansson"), @person.name
|
19
|
+
end
|
20
|
+
|
21
|
+
test "writing name" do
|
22
|
+
@person.name = PersonName.new("Jason", "Fried")
|
23
|
+
assert_equal PersonName.new("Jason", "Fried"), @person.name
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/testing/autorun'
|
3
|
+
|
4
|
+
require 'name_of_person/loaders/active_model_has_person_name'
|
5
|
+
|
6
|
+
class ModelPerson
|
7
|
+
include ActiveModel::Model
|
8
|
+
attr_accessor :first_name, :last_name
|
9
|
+
|
10
|
+
has_person_name
|
11
|
+
end
|
12
|
+
|
13
|
+
class HasPersonNameTest < ActiveSupport::TestCase
|
14
|
+
include NameOfPerson
|
15
|
+
|
16
|
+
setup do
|
17
|
+
@person = ModelPerson.new(first_name: "David", last_name: "Heinemeier Hansson")
|
18
|
+
end
|
19
|
+
|
20
|
+
test "reading name" do
|
21
|
+
assert_equal PersonName.new("David", "Heinemeier Hansson"), @person.name
|
22
|
+
end
|
23
|
+
|
24
|
+
test "writing name" do
|
25
|
+
@person.name = PersonName.new("Jason", "Fried")
|
26
|
+
assert_equal PersonName.new("Jason", "Fried"), @person.name
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/testing/autorun'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'name_of_person/person_name'
|
6
|
+
|
7
|
+
class PersonNameTest < ActiveSupport::TestCase
|
8
|
+
include NameOfPerson
|
9
|
+
|
10
|
+
setup do
|
11
|
+
@name = PersonName.new('Foo', 'Bar')
|
12
|
+
@first = PersonName.new('Baz')
|
13
|
+
end
|
14
|
+
|
15
|
+
test "first is required" do
|
16
|
+
assert_raise ArgumentError do
|
17
|
+
PersonName.new(nil)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
test "last may be omitted" do
|
22
|
+
assert_nothing_raised do
|
23
|
+
PersonName.new('Foo', nil)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
test "first and last" do
|
28
|
+
assert_equal 'Foo', @name.first
|
29
|
+
assert_equal 'Bar', @name.last
|
30
|
+
|
31
|
+
assert_equal 'Baz', @first.first
|
32
|
+
assert_nil @first.last
|
33
|
+
end
|
34
|
+
|
35
|
+
test "full name" do
|
36
|
+
assert_equal 'Foo Bar', @name.full
|
37
|
+
assert_equal 'Foo Bar', @name
|
38
|
+
|
39
|
+
assert_equal @first.first, @first.full
|
40
|
+
assert_equal @first.first, @first
|
41
|
+
end
|
42
|
+
|
43
|
+
test "abbreviations" do
|
44
|
+
assert_equal 'Foo B.', @name.familiar
|
45
|
+
assert_equal 'F. Bar', @name.abbreviated
|
46
|
+
assert_equal 'Bar, Foo', @name.sorted
|
47
|
+
|
48
|
+
assert_equal @first.first, @first.familiar
|
49
|
+
assert_equal @first.first, @first.abbreviated
|
50
|
+
assert_equal @first.first, @first.sorted
|
51
|
+
end
|
52
|
+
|
53
|
+
test "possessive" do
|
54
|
+
assert_equal "#{@name.full}'s", @name.possessive
|
55
|
+
assert_equal "#{@first.full}'s", @first.possessive
|
56
|
+
assert_equal "Foo Bars'", PersonName.new('Foo', 'Bars').possessive
|
57
|
+
end
|
58
|
+
|
59
|
+
test "from full name" do
|
60
|
+
name = PersonName.full('Will St. Clair')
|
61
|
+
assert_equal 'Will', name.first
|
62
|
+
assert_equal 'St. Clair', name.last
|
63
|
+
|
64
|
+
first = PersonName.full('Will')
|
65
|
+
assert_equal 'Will', first.first
|
66
|
+
assert_nil first.last
|
67
|
+
|
68
|
+
assert_nil PersonName.full(nil)
|
69
|
+
assert_nil PersonName.full('')
|
70
|
+
end
|
71
|
+
|
72
|
+
test "blank last name behaves the same as nil" do
|
73
|
+
name = PersonName.new('Baz', '')
|
74
|
+
assert_equal @first.full, name.full
|
75
|
+
assert_equal @first.familiar, name.familiar
|
76
|
+
assert_equal @first.abbreviated, name.abbreviated
|
77
|
+
assert_equal @first.sorted, name.sorted
|
78
|
+
assert_equal @first.possessive, name.possessive
|
79
|
+
end
|
80
|
+
|
81
|
+
test "mentionable" do
|
82
|
+
assert_equal 'foob', @name.mentionable
|
83
|
+
end
|
84
|
+
|
85
|
+
test "mentionable with three names" do
|
86
|
+
name = PersonName.full('Will St. Clair')
|
87
|
+
assert_equal 'wills', name.mentionable
|
88
|
+
end
|
89
|
+
|
90
|
+
test "familiar" do
|
91
|
+
assert_equal 'Foo B.', @name.familiar
|
92
|
+
end
|
93
|
+
|
94
|
+
test "familiar with three names" do
|
95
|
+
name = PersonName.full('Will St. Clair')
|
96
|
+
assert_equal 'Will S.', name.familiar
|
97
|
+
end
|
98
|
+
|
99
|
+
test "serialize yaml" do
|
100
|
+
assert_equal @name, YAML.load(YAML.dump(@name))
|
101
|
+
assert_equal @first, YAML.load(YAML.dump(@first))
|
102
|
+
assert_equal String, YAML.load(YAML.dump(@name)).class
|
103
|
+
end
|
104
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: name_of_person
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Heinemeier Hansson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.15'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.15'
|
55
|
+
description:
|
56
|
+
email: david@basecamp.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- Gemfile
|
62
|
+
- Gemfile.lock
|
63
|
+
- MIT-LICENSE
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- lib/name_of_person.rb
|
67
|
+
- lib/name_of_person/assignable_name.rb
|
68
|
+
- lib/name_of_person/has_person_name.rb
|
69
|
+
- lib/name_of_person/loaders/active_model_has_person_name.rb
|
70
|
+
- lib/name_of_person/loaders/active_record_has_person_name.rb
|
71
|
+
- lib/name_of_person/person_name.rb
|
72
|
+
- name_of_person.gemspec
|
73
|
+
- test/assignable_name_test.rb
|
74
|
+
- test/has_person_name_test.rb
|
75
|
+
- test/person_name_test.rb
|
76
|
+
homepage: https://github.com/basecamp/name_of_person
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.4.0
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.7.6
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Presenting names of people in full, familiar, abbreviated, and initialized
|
100
|
+
forms (but without titulation etc)
|
101
|
+
test_files:
|
102
|
+
- test/assignable_name_test.rb
|
103
|
+
- test/has_person_name_test.rb
|
104
|
+
- test/person_name_test.rb
|