openopus-core-people 1.1.0 → 1.1.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: fb6c5d918b4cf8dc73c3c5a22503673dc1ddf9558a8447ef1a505154007753d9
4
- data.tar.gz: e749bc852fe3e432d4aae6e3ce33c04135a94fca16cda43c2ccaecc25b3e0942
3
+ metadata.gz: 6ab650c65f3c0c4bbf10f15ed0a6c0ce275beb9133e6d3ec56577ed043760508
4
+ data.tar.gz: 94d9e71f0cdb9e4e26a66f0426e8d9b73839c7ce2c1b9e0aee97883331e6fc50
5
5
  SHA512:
6
- metadata.gz: 5abc7901b229c1850154f628bb6c6efe47bfe3e39ebbf45c0e7d49c4a9286f48c5a1b48af70aaff01fe40ce85d1a148b9f166e83c90fbf371f0eb816aa5f252c
7
- data.tar.gz: 9c08ec78f0de97a8e880295e6e01a7db12471371c59c6db043533986d041bef2317fdbee48c7ba6c78a6c1317a6fca432a5641ee75e04d828efdc959ff8c43f9
6
+ metadata.gz: 8269bb3d92b704b885ca77b85909c0111ea6a64472aefa646da6c2d114efd033bb0282c150972ac49de813ef101f2ba9ab8b2f679cda9c3d6fd77c4402d11144
7
+ data.tar.gz: c2cdf46236104f440f932b417ff45f6ff300b629d162dd7d35deada4867ac14a8c224aa53c42b5f2357b60732c46a53c16797e6864232991342b35f006368dd1
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/opuslogica/openopus-core-people"
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
- $ bundle exec rake db:migrate
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|
@@ -1,6 +1,6 @@
1
1
  class Address < ApplicationRecord
2
- belongs_to :label, optional: true
3
- belongs_to :addressable, polymorphic: true
2
+ include Labelize
3
+ belongs_to :addressable, polymorphic: true, optional: true
4
4
 
5
5
  accepts_nested_attributes_for :label
6
6
 
@@ -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
@@ -1,17 +1,27 @@
1
1
  class Email < ApplicationRecord
2
- belongs_to :label, optional: true
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
@@ -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
- people = self.all.collect do |p|
18
- [p.id, [(p.fname[0] || ""), (p.minitial[0] || ""), (p.lname[0] || "")].join("").upcase]
19
- end
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
@@ -1,18 +1,23 @@
1
+ require "phony_rails"
2
+
1
3
  class Phone < ApplicationRecord
2
- belongs_to :label, optional: true
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 canonical[0] != "+"
12
- digits = digits_and_stuff.gsub(/[^0-9]/, "")
13
- digits = digits[1..-1] if digits[0] == '1'
14
- digits = "805" + digits if digits.length == 7
15
- canonical = "(#{digits[0..2]}) #{digits[3..5]}-#{digits[6..10]}"
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
@@ -1,25 +1,18 @@
1
1
  class User < ApplicationRecord
2
2
  belongs_to :person
3
3
  accepts_nested_attributes_for :person
4
- has_many :credentials, as: :credentialed, dependent: :destroy
5
- accepts_nested_attributes_for :credentials
6
4
  has_and_belongs_to_many :organizations
7
5
 
8
- delegate :name, :name=, to: :person
9
- delegate :fname, :fname=, to: :person
10
- delegate :lname, :lname, to: :person
11
- delegate :minitial, :minitial, to: :person
12
- delegate :prefix, :prefix, to: :person
13
- delegate :suffix, :suffix, to: :person
14
- delegate :emails, :emails=, :email, :email=, to: :person
15
- delegate :phones, :phones=, :phone, :phone=, to: :person
16
- delegate :addresses, :addresses=, :address, :address=, to: :person
17
- delegate :birthdate, :birthdate=, to: :person
18
- 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)
19
11
 
20
12
  def self.lookup(item)
21
13
  person = Person.lookup(item)
22
- (person.send self.name.downcase.to_sym) if person
14
+ this_class = self.name.downcase.to_sym
15
+ (person.send this_class) if person and person.respond_to?(this_class)
23
16
  end
24
17
 
25
18
  def organization=(org)
@@ -29,4 +22,11 @@ class User < ApplicationRecord
29
22
  def organization
30
23
  self.organizations.order(created_at: :desc).first
31
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
32
32
  end
@@ -8,6 +8,7 @@ class CreatePeople < ActiveRecord::Migration[5.2]
8
8
  t.string :suffix, limit: 10
9
9
  t.string :birthdate
10
10
  t.string :nationality
11
+ t.string :language, limit: 10
11
12
 
12
13
  t.timestamps
13
14
  end
@@ -4,5 +4,4 @@ bfox = User.lookup("bfox@opuslogica.com") ||
4
4
  User.create(person_attributes: { email: "bfox@opuslogica.com", name: "Brian Jhan Fox",
5
5
  address: "901 Olive St., Santa Barbara, CA, 93101",
6
6
  phone: "805.555.8642" },
7
- organization: opus,
8
- credentials_attributes: [{ password: "idtmp2tv" }])
7
+ organization: opus)
@@ -2,7 +2,6 @@ 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|
@@ -1,7 +1,7 @@
1
1
  module Openopus
2
2
  module Core
3
3
  module People
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.11'
5
5
  end
6
6
  end
7
7
  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,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openopus-core-people
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.11
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: 2019-07-13 00:00:00.000000000 Z
11
+ date: 2020-12-26 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
27
  - !ruby/object:Gem::Dependency
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 3.1.13
41
+ - !ruby/object:Gem::Dependency
42
+ name: phony_rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.14'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.14'
41
55
  description: A person can have many email addresses, but this is not usually represented
42
56
  in applications. openopus/core/people creates the database structure, relations,
43
57
  and convenience functions for your application so you don't have to. Just connect
@@ -53,7 +67,7 @@ files:
53
67
  - Rakefile
54
68
  - app/assets/config/openopus_core_people_manifest.js
55
69
  - app/models/address.rb
56
- - app/models/credential.rb
70
+ - app/models/concerns/labelize.rb
57
71
  - app/models/email.rb
58
72
  - app/models/label.rb
59
73
  - app/models/nickname.rb
@@ -70,14 +84,13 @@ files:
70
84
  - db/migrate/00000000000005_create_people.rb
71
85
  - db/migrate/00000000000006_create_organizations.rb
72
86
  - db/migrate/00000000000007_create_users.rb
73
- - db/migrate/00000000000008_create_credentials.rb
74
87
  - db/migrate/00000000000009_create_join_table_organizations_users.rb
75
88
  - db/seeds.rb
76
89
  - lib/openopus/core/people.rb
77
90
  - lib/openopus/core/people/engine.rb
78
91
  - lib/openopus/core/people/version.rb
79
- - lib/tasks/openopus/core/people_tasks.rake
80
- homepage: https://github.com/opuslogica/openopus-core-people
92
+ - lib/tasks/openopus/core/people/tasks.rake
93
+ homepage: https://github.com/openopus/openopus-core-people
81
94
  licenses:
82
95
  - MIT
83
96
  metadata:
@@ -97,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
110
  - !ruby/object:Gem::Version
98
111
  version: '0'
99
112
  requirements: []
100
- rubygems_version: 3.0.1
113
+ rubygems_version: 3.1.2
101
114
  signing_key:
102
115
  specification_version: 4
103
116
  summary: Model the real world of people in your application, making user interaction
@@ -1,15 +0,0 @@
1
- class Credential < ApplicationRecord
2
- has_secure_password
3
- belongs_to :credentialed, polymorphic: true
4
-
5
- # We point to an enail that actually polymorphically belongs to a different thing.
6
- # Which is great. So don't declare this to be a polymorphic relationship. It isn't.
7
- belongs_to :email
8
- before_validation :grab_email_from_credentialed
9
-
10
- # validates :password, length: { minimum: 8 }, on: :create
11
-
12
- def grab_email_from_credentialed
13
- self.email ||= self.credentialed.email
14
- end
15
- end
@@ -1,13 +0,0 @@
1
- class CreateCredentials < ActiveRecord::Migration[5.2]
2
- def change
3
- create_table :credentials do |t|
4
- t.references :credentialed, polymorphic: true
5
- t.references :email, foreign_key: true
6
- t.string :password_digest
7
- t.string :provider # Like Google, Facebook, etc.
8
- t.string :provider_auth # Whatever their auth token looks like.
9
-
10
- t.timestamps
11
- end
12
- end
13
- end
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :openopus_core_people do
3
- # # Task goes here
4
- # end