openopus-core-people 1.1.1 → 1.1.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -2
- data/Rakefile +2 -3
- data/app/models/address.rb +2 -2
- data/app/models/concerns/labelize.rb +26 -0
- data/app/models/email.rb +13 -3
- data/app/models/person.rb +46 -14
- data/app/models/phone.rb +28 -9
- data/app/models/user.rb +14 -12
- data/db/migrate/00000000000005_create_people.rb +1 -0
- data/lib/openopus/core/people/engine.rb +1 -2
- data/lib/openopus/core/people/version.rb +1 -1
- data/lib/tasks/openopus/core/people/tasks.rake +44 -0
- metadata +22 -8
- data/lib/openopus/core/people/#engine.rb# +0 -16
- data/lib/tasks/openopus/core/people_tasks.rake +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a1210e0ee30df4b15de2054b11faaa01c73aab0c2756acafbe6541bf2dc16b0
|
4
|
+
data.tar.gz: 39678b84725ad8c7b320690492a1d5e97e6902ebb05e1ab9160faf5ee90348ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61d3d9c7542d3b535ddee482e36f76a88c6e6c763c1504893f083281053146fd6b6c3f2fb87cf8a404ccfca35fef5c305e3bfc2bdacb876af360c21d52d1a5da
|
7
|
+
data.tar.gz: e936e3bde4f9a3a742c11cec779ceb3f9aa4bc6c4405e8764a49b9046eaf5681d866f80d57be25fc69650ae0e471f2652e3c1dbe108e42489f3ca5c18c7fb4bc
|
data/README.md
CHANGED
@@ -17,13 +17,14 @@ Install the gem, run migrations. This will create human structures, from Organi
|
|
17
17
|
Add this line to your application's Gemfile:
|
18
18
|
|
19
19
|
```ruby
|
20
|
-
gem 'openopus-core-people', git: "https://github.com/
|
20
|
+
gem 'openopus-core-people', git: "https://github.com/openopus/openopus-core-people"
|
21
21
|
```
|
22
22
|
|
23
23
|
And then execute:
|
24
24
|
```bash
|
25
25
|
$ bundle install
|
26
|
-
$
|
26
|
+
$ ./bin/rake openopus_core_people_engine:install:migrations
|
27
|
+
$ ./bin/rake db:migrate
|
27
28
|
```
|
28
29
|
|
29
30
|
Or install it yourself as:
|
data/Rakefile
CHANGED
@@ -15,12 +15,11 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
18
|
-
load 'rails/tasks/engine.rake'
|
19
|
-
|
18
|
+
# load 'rails/tasks/engine.rake'
|
20
19
|
load 'rails/tasks/statistics.rake'
|
20
|
+
load 'lib/tasks/openopus/core/people/tasks.rake'
|
21
21
|
|
22
22
|
require 'bundler/gem_tasks'
|
23
|
-
|
24
23
|
require 'rake/testtask'
|
25
24
|
|
26
25
|
Rake::TestTask.new(:test) do |t|
|
data/app/models/address.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
# labelize.rb: -*- Ruby -*- DESCRIPTIVE TEXT.
|
2
|
+
#
|
3
|
+
# Copyright (c) 2019 Brian J. Fox Opus Logica, Inc.
|
4
|
+
# Author: Brian J. Fox (bfox@opuslogica.com)
|
5
|
+
# Birthdate: Wed Sep 11 09:37:52 2019.
|
6
|
+
module Labelize
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
included do
|
9
|
+
belongs_to :label, optional: true
|
10
|
+
|
11
|
+
def label
|
12
|
+
db_label = super
|
13
|
+
if not db_label
|
14
|
+
self.label = "Work"
|
15
|
+
db_label = super
|
16
|
+
end
|
17
|
+
|
18
|
+
db_label.value
|
19
|
+
end
|
20
|
+
|
21
|
+
def label=(name)
|
22
|
+
super(Label.get(name))
|
23
|
+
self.label
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/app/models/email.rb
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
class Email < ApplicationRecord
|
2
|
-
|
3
|
-
belongs_to :emailable, polymorphic: true
|
2
|
+
include Labelize
|
3
|
+
belongs_to :emailable, polymorphic: true, optional: true
|
4
4
|
accepts_nested_attributes_for :label
|
5
5
|
before_create :default_label
|
6
|
+
before_save :canonicalize
|
7
|
+
|
8
|
+
VALID_EMAIL_FORMAT_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
|
9
|
+
|
10
|
+
validates_format_of :address, with: VALID_EMAIL_FORMAT_REGEX, :on => :create
|
6
11
|
|
7
12
|
def self.canonicalize(addr)
|
8
|
-
addr.strip.downcase if not addr.blank?
|
13
|
+
candidate = addr.strip.downcase if not addr.blank?
|
14
|
+
candidate ||= addr
|
9
15
|
end
|
10
16
|
|
11
17
|
def canonicalize
|
12
18
|
self.address = self.class.canonicalize(self.address)
|
13
19
|
end
|
14
20
|
|
21
|
+
def self.valid_format?(addr)
|
22
|
+
return (addr =~ VALID_EMAIL_FORMAT_REGEX) != nil
|
23
|
+
end
|
24
|
+
|
15
25
|
def default_label
|
16
26
|
self.label = Label.get("Work")
|
17
27
|
end
|
data/app/models/person.rb
CHANGED
@@ -11,12 +11,25 @@ class Person < ApplicationRecord
|
|
11
11
|
accepts_nested_attributes_for :emails
|
12
12
|
|
13
13
|
def self.lookup(name)
|
14
|
+
return nil if not name
|
14
15
|
person = self.find_by(self.name_components(name))
|
15
16
|
person ||= self.includes(:nicknames).joins(:nicknames).find_by("nicknames.nickname" => name)
|
17
|
+
|
18
|
+
# Maybe we're looking up by phone number?
|
16
19
|
if not person
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
phone = Phone.where(number: Phone.canonicalize(name)).first rescue nil
|
21
|
+
person = phone.phoneable if phone
|
22
|
+
end
|
23
|
+
|
24
|
+
if not person
|
25
|
+
email = Email.where(emailable_type: self.name.to_s, address: Email.canonicalize(name.downcase)).first
|
26
|
+
person = email.emailable if email
|
27
|
+
end
|
28
|
+
|
29
|
+
if not person
|
30
|
+
# Try hard to find a person by their initials, even if there wasn't a nickname for them.
|
31
|
+
people = self.all.collect {|p| [p.id, p.initials]}
|
32
|
+
|
20
33
|
people.each do |parry|
|
21
34
|
if parry[1] == name.upcase
|
22
35
|
person = self.find(parry[0])
|
@@ -25,11 +38,6 @@ class Person < ApplicationRecord
|
|
25
38
|
end
|
26
39
|
end
|
27
40
|
|
28
|
-
if not person
|
29
|
-
addr = Email.where(emailable_type: self.name.to_s, address: Email.canonicalize(name.downcase)).first
|
30
|
-
person = addr.person.first if addr
|
31
|
-
end
|
32
|
-
|
33
41
|
person
|
34
42
|
end
|
35
43
|
|
@@ -82,7 +90,7 @@ class Person < ApplicationRecord
|
|
82
90
|
result = possibles.include?(text.gsub(/[.]*/, "").downcase) if text.present?
|
83
91
|
end
|
84
92
|
|
85
|
-
def self.name_components(name)
|
93
|
+
def self.name_components(name, transformer=nil)
|
86
94
|
res = {}
|
87
95
|
component = ""
|
88
96
|
components = name.gsub(/,/, " ").gsub(/ /, " ").split(" ") rescue [name]
|
@@ -92,37 +100,56 @@ class Person < ApplicationRecord
|
|
92
100
|
# What kind of thing is this?
|
93
101
|
if is_name_prefix?(component)
|
94
102
|
res[:prefix] = component
|
103
|
+
res[:prefix] = res[:prefix].send(transformer) if res[:prefix] && transformer
|
95
104
|
res[:fname] = components.shift
|
96
105
|
else
|
97
106
|
res[:fname] = component
|
98
107
|
end
|
99
108
|
|
109
|
+
res[:fname] = res[:fname].send(transformer) if res[:fname] && transformer
|
110
|
+
|
100
111
|
# Next up, middle initial or last name.
|
101
112
|
# If only one word remains, that's the last name
|
102
113
|
if components.length == 1
|
103
114
|
res[:lname] = components.shift
|
115
|
+
res[:lname] = res[:lname].send(transformer) if res[:lname] && transformer
|
104
116
|
elsif components.length > 0
|
105
117
|
# At least 2 words remain. We might have middle names, prefixes, suffixes, etc.
|
106
118
|
components.reverse!
|
107
119
|
component = components.shift
|
120
|
+
|
108
121
|
if is_name_suffix?(component)
|
109
122
|
res[:suffix] = component
|
123
|
+
res[:suffix] = res[:suffix].send(transformer) if res[:suffix] && transformer
|
110
124
|
res[:lname] = components.shift
|
111
125
|
else
|
112
126
|
res[:lname] = component
|
113
127
|
end
|
128
|
+
|
129
|
+
res[:lname] = res[:lname].send(transformer) if res[:lname] && transformer
|
130
|
+
|
114
131
|
res[:minitial] = components.shift
|
132
|
+
res[:minitial] = res[:minitial].send(transformer) if res[:minitial] && transformer
|
133
|
+
|
115
134
|
end
|
116
135
|
|
117
136
|
res[:minitial] = res[:minitial].gsub(/[.]/, "") if res[:minitial].present?
|
118
137
|
res
|
119
138
|
end
|
120
|
-
|
139
|
+
|
140
|
+
def initials
|
141
|
+
res = ""
|
142
|
+
res += fname[0] if fname
|
143
|
+
res += minitial[0] if minitial
|
144
|
+
res += lname[0] if lname
|
145
|
+
res.upcase
|
146
|
+
end
|
147
|
+
|
121
148
|
def name
|
122
149
|
components = []
|
123
150
|
|
124
151
|
if prefix.present?
|
125
|
-
if not prefix.include?(".")
|
152
|
+
if not prefix.include?(".") and not prefix =~ /^miss$/i
|
126
153
|
components << "#{prefix}."
|
127
154
|
else
|
128
155
|
components << prefix
|
@@ -231,8 +258,8 @@ class Person < ApplicationRecord
|
|
231
258
|
|
232
259
|
def add_address(address_line, label="Home")
|
233
260
|
if self != self.class.find_by_address(address_line)
|
234
|
-
a = Address.parse(address_line)
|
235
|
-
a.addressable_type = self.class.name
|
261
|
+
a = Address.parse(address_line)
|
262
|
+
a.addressable_type = self.class.name
|
236
263
|
a.label = Label.get(label)
|
237
264
|
a.save
|
238
265
|
addresses << a
|
@@ -240,5 +267,10 @@ class Person < ApplicationRecord
|
|
240
267
|
a = Address.find_by_address(address_line)
|
241
268
|
end
|
242
269
|
|
243
|
-
|
270
|
+
def as_api_json(options={})
|
271
|
+
candidate = self.as_json(options)
|
272
|
+
candidate[:emails] = self.emails.collect {|e| { label: e.label, address: e.address }}
|
273
|
+
candidate[:addresses] = self.addresses.collect {|a| { label: a.label, address: a.address }}
|
274
|
+
candidate
|
275
|
+
end
|
244
276
|
end
|
data/app/models/phone.rb
CHANGED
@@ -1,18 +1,23 @@
|
|
1
|
+
require "phony_rails"
|
2
|
+
|
1
3
|
class Phone < ApplicationRecord
|
2
|
-
|
3
|
-
belongs_to :phoneable, polymorphic: true
|
4
|
+
include Labelize
|
5
|
+
belongs_to :phoneable, polymorphic: true, optional: true
|
4
6
|
before_validation :canonicalize
|
5
7
|
|
6
8
|
def self.canonicalize(digits_and_stuff)
|
7
|
-
canonical = digits_and_stuff
|
8
|
-
canonical.gsub!(" ", "") #remove extra spaces
|
9
|
+
canonical = digits_and_stuff.gsub(" ", "")
|
9
10
|
if canonical
|
10
11
|
canonical = canonical[2..100].strip if canonical.starts_with?("+1")
|
11
|
-
if
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
if self.const_defined?("PhonyRails")
|
13
|
+
canonical = PhonyRails.normalize_number(digits_and_stuff, default_country_code: "US").phony_formatted(format: :international)
|
14
|
+
else
|
15
|
+
if canonical[0] != "+"
|
16
|
+
digits = digits_and_stuff.gsub(/[^0-9]/, "")
|
17
|
+
digits = digits[1..-1] if digits[0] == '1'
|
18
|
+
digits = "805" + digits if digits.length == 7
|
19
|
+
canonical = "(#{digits[0..2]}) #{digits[3..5]}-#{digits[6..10]}"
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
18
23
|
canonical
|
@@ -23,4 +28,18 @@ class Phone < ApplicationRecord
|
|
23
28
|
self.number = self.class.canonicalize(self.number) if self.number
|
24
29
|
end
|
25
30
|
|
31
|
+
def label
|
32
|
+
db_label = super
|
33
|
+
if not db_label
|
34
|
+
self.label = "Work"
|
35
|
+
db_label = super
|
36
|
+
end
|
37
|
+
|
38
|
+
db_label.value
|
39
|
+
end
|
40
|
+
|
41
|
+
def label=(name)
|
42
|
+
super(Label.get(name))
|
43
|
+
self.label
|
44
|
+
end
|
26
45
|
end
|
data/app/models/user.rb
CHANGED
@@ -3,21 +3,16 @@ class User < ApplicationRecord
|
|
3
3
|
accepts_nested_attributes_for :person
|
4
4
|
has_and_belongs_to_many :organizations
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
delegate
|
11
|
-
delegate :suffix, :suffix, to: :person
|
12
|
-
delegate :emails, :emails=, :email, :email=, to: :person
|
13
|
-
delegate :phones, :phones=, :phone, :phone=, to: :person
|
14
|
-
delegate :addresses, :addresses=, :address, :address=, to: :person
|
15
|
-
delegate :birthdate, :birthdate=, to: :person
|
16
|
-
delegate :age, :age=, to: :person
|
6
|
+
PERSON_ATTR = %w(name fname lname minitial prefix suffix email phone emails phones addresses birthdate age nationality language)
|
7
|
+
PERSON_SYMS = PERSON_ATTR.collect {|x| [x.to_sym, (x + "=").to_sym] }.flatten
|
8
|
+
PERSON_SET = PERSON_SYMS + PERSON_ATTR
|
9
|
+
|
10
|
+
delegate(*PERSON_SYMS, to: :person)
|
17
11
|
|
18
12
|
def self.lookup(item)
|
19
13
|
person = Person.lookup(item)
|
20
|
-
|
14
|
+
this_class = self.name.downcase.to_sym
|
15
|
+
(person.send this_class) if person and person.respond_to?(this_class)
|
21
16
|
end
|
22
17
|
|
23
18
|
def organization=(org)
|
@@ -27,4 +22,11 @@ class User < ApplicationRecord
|
|
27
22
|
def organization
|
28
23
|
self.organizations.order(created_at: :desc).first
|
29
24
|
end
|
25
|
+
|
26
|
+
def assign_attributes(attr)
|
27
|
+
if not attr.keys.to_set.intersection(PERSON_SET).empty?
|
28
|
+
self.person = Person.new
|
29
|
+
end
|
30
|
+
super(attr)
|
31
|
+
end
|
30
32
|
end
|
@@ -2,11 +2,10 @@ module Openopus
|
|
2
2
|
module Core
|
3
3
|
module People
|
4
4
|
class Engine < ::Rails::Engine
|
5
|
-
isolate_namespace People
|
6
5
|
initializer :append_migrations do |app|
|
7
6
|
unless app.root.to_s.match(root.to_s)
|
8
7
|
config.paths["db/migrate"].expanded.each do |expanded_path|
|
9
|
-
app.config.paths["db/migrate"] << expanded_path
|
8
|
+
# app.config.paths["db/migrate"] << expanded_path
|
10
9
|
end
|
11
10
|
end
|
12
11
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
namespace :openopus do
|
2
|
+
namespace :core do
|
3
|
+
namespace :people do
|
4
|
+
desc "Generate one or many random people in your database. Requires 'gem rest-client'. Only for testing!"
|
5
|
+
task :generate_people, [:count] => :environment do |task, args|
|
6
|
+
def generate_person
|
7
|
+
json = ActiveSupport::HashWithIndifferentAccess.new(JSON(RestClient::Request.execute(method: :get, url: 'https://randomuser.me/api?format=json&nat=US').body))
|
8
|
+
jp = json[:results][0]
|
9
|
+
l = jp[:location]
|
10
|
+
a = Address.new
|
11
|
+
a.line1 = l[:street].titleize; a.city = l[:city].titleize; a.state = l[:state].titleize;
|
12
|
+
a.postal = l[:postcode].to_s.upcase; a.country = "US"
|
13
|
+
name = jp[:name]
|
14
|
+
p = Person.create(name: "#{name[:title]} #{name[:first]} #{name[:last]}".titleize,
|
15
|
+
phone: jp[:phone], address: a, email: jp[:email],
|
16
|
+
birthdate: jp[:dob][:date], nationality: jp[:nat])
|
17
|
+
p.picture = jp[:picture][:medium] if p.respond_to?("picture=".to_sym)
|
18
|
+
p.photo = jp[:picture][:medium] if p.respond_to?("photo=".to_sym)
|
19
|
+
p
|
20
|
+
end
|
21
|
+
|
22
|
+
count = args[:count]
|
23
|
+
count ||= 1
|
24
|
+
count.to_i.times do
|
25
|
+
person = generate_person
|
26
|
+
puts("openopus made: #{person.name} of #{person.address.oneline}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Generate users from the people in your database that aren't associated with any user. Only for testing!"
|
31
|
+
task :generate_users, [:count] => :environment do |task, args|
|
32
|
+
def generate_user(person)
|
33
|
+
user = User.create(person_id: person.id, status: "generated") if not person.user and not person.email.blank?
|
34
|
+
if user and user.respond_to?(:credentials) and user.credentials.blank?
|
35
|
+
user.credentials.create(password: "password")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::Task["openopus:core:people:generate_people"].invoke(args[:count]) if not args[:count].blank?
|
40
|
+
Person.all.each { |person| generate_user(person) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openopus-core-people
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian J. Fox
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 5.2.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 5.2.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: phony_rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.14'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.14'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bcrypt
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,6 +67,7 @@ files:
|
|
53
67
|
- Rakefile
|
54
68
|
- app/assets/config/openopus_core_people_manifest.js
|
55
69
|
- app/models/address.rb
|
70
|
+
- app/models/concerns/labelize.rb
|
56
71
|
- app/models/email.rb
|
57
72
|
- app/models/label.rb
|
58
73
|
- app/models/nickname.rb
|
@@ -72,11 +87,10 @@ files:
|
|
72
87
|
- db/migrate/00000000000009_create_join_table_organizations_users.rb
|
73
88
|
- db/seeds.rb
|
74
89
|
- lib/openopus/core/people.rb
|
75
|
-
- lib/openopus/core/people/#engine.rb#
|
76
90
|
- lib/openopus/core/people/engine.rb
|
77
91
|
- lib/openopus/core/people/version.rb
|
78
|
-
- lib/tasks/openopus/core/
|
79
|
-
homepage: https://github.com/
|
92
|
+
- lib/tasks/openopus/core/people/tasks.rake
|
93
|
+
homepage: https://github.com/openopus/openopus-core-people
|
80
94
|
licenses:
|
81
95
|
- MIT
|
82
96
|
metadata:
|
@@ -96,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
110
|
- !ruby/object:Gem::Version
|
97
111
|
version: '0'
|
98
112
|
requirements: []
|
99
|
-
rubygems_version: 3.
|
113
|
+
rubygems_version: 3.1.4
|
100
114
|
signing_key:
|
101
115
|
specification_version: 4
|
102
116
|
summary: Model the real world of people in your application, making user interaction
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module Openopus
|
2
|
-
module Core
|
3
|
-
module People
|
4
|
-
class Engine < ::Rails::Engine
|
5
|
-
isolate_namespace People
|
6
|
-
initializer :append_migrations do |app|
|
7
|
-
unless app.root.to_s.match(root.to_s)
|
8
|
-
config.paths["db/migrate"].expanded.each do |expanded_path|
|
9
|
-
app.config.paths["db/migrate"] << expanded_path
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|