openopus-core-people 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb6c5d918b4cf8dc73c3c5a22503673dc1ddf9558a8447ef1a505154007753d9
4
+ data.tar.gz: e749bc852fe3e432d4aae6e3ce33c04135a94fca16cda43c2ccaecc25b3e0942
5
+ SHA512:
6
+ metadata.gz: 5abc7901b229c1850154f628bb6c6efe47bfe3e39ebbf45c0e7d49c4a9286f48c5a1b48af70aaff01fe40ce85d1a148b9f166e83c90fbf371f0eb816aa5f252c
7
+ data.tar.gz: 9c08ec78f0de97a8e880295e6e01a7db12471371c59c6db043533986d041bef2317fdbee48c7ba6c78a6c1317a6fca432a5641ee75e04d828efdc959ff8c43f9
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Brian J. Fox
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,38 @@
1
+ # Openopus::Core::People
2
+ Model the real world of people in your application, making user interaction robust. By way of example, a person can have many email addresses, but this is not usually represented in applications. openopus/core/people creates the database structure, relations, and convenience functions for your application so you don't have to. Just connect your end user model, and away you go.
3
+
4
+ ## Usage
5
+ Install the gem, run migrations. This will create human structures, from Organization down to people (Person). More documentation and usage examples will be forthcoming.
6
+
7
+ ```ruby
8
+ opus = Organization.where(name: "Opus Logica, Inc").first_or_create(nicknames_attributes: [{ nickname: "OPUS" }])
9
+ bfox = User.lookup("bfox@opuslogica.com") ||
10
+ User.create(person_attributes: { email: "bfox@opuslogica.com", name: "Brian Jhan Fox",
11
+ address: "901 Olive St., Santa Barbara, CA, 93101",
12
+ phone: "805.555.8642" },
13
+ organization: opus,
14
+ credentials_attributes: [{ password: "idtmp2tv" }])
15
+ ```
16
+ ## Installation
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'openopus-core-people', git: "https://github.com/opuslogica/openopus-core-people"
21
+ ```
22
+
23
+ And then execute:
24
+ ```bash
25
+ $ bundle install
26
+ $ bundle exec rake db:migrate
27
+ ```
28
+
29
+ Or install it yourself as:
30
+ ```bash
31
+ $ gem install openopus-core-people
32
+ ```
33
+
34
+ ## Contributing
35
+ Got an idea for an improvement? Think this thing is *almost* good, but still kinda sucks? Create an issue, and we'll get back to you ASAP. Please discuss code contributions with us before hand so that we can agree on the architecture. Otherwise, we might not be able to take your improvements.
36
+
37
+ ## License
38
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Openopus::Core::People'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,168 @@
1
+ class Address < ApplicationRecord
2
+ belongs_to :label, optional: true
3
+ belongs_to :addressable, polymorphic: true
4
+
5
+ accepts_nested_attributes_for :label
6
+
7
+ # require 'carmen'
8
+ # require 'geocoder'
9
+ # geocoded_by :oneline, latitude: :lat, longitude: :lon
10
+ # after_validation :geocode
11
+
12
+ def address
13
+ oneline
14
+ end
15
+
16
+ def save(options={})
17
+ r = super(options)
18
+ @already_geocoded = false
19
+ r
20
+ end
21
+
22
+ def assign_attributes(hash)
23
+ if hash.has_key?('address')
24
+ a = hash['address']
25
+ hash.delete('address')
26
+ end
27
+ super(hash)
28
+ self.address = a if a
29
+ nil
30
+ end
31
+
32
+ def label=(text_or_label)
33
+ l = Label.get(text_or_label.to_s) if text_or_label.is_a?(String) or text_or_label.is_a?(Symbol)
34
+ l ||= text_or_label
35
+ write_attribute(:label_id, l.id)
36
+ end
37
+
38
+ def update_lat_lon(info=nil)
39
+ info ||= Geocoder.search(self.oneline).first rescue nil
40
+ if info
41
+ if info.respond_to?(:latitude)
42
+ self.lat = info.latitude
43
+ self.lon = info.longitude
44
+ else
45
+ self.lat = info.geometry["location"]["lat"] rescue nil
46
+ self.lon = info.geometry["location"]["lng"] rescue nil
47
+ end
48
+ end
49
+ end
50
+
51
+ def address=(string)
52
+ return unless not string.blank? and not postal_changed?
53
+ return unless string != line1
54
+ return if (string == oneline() || string == oneline({full: true}))
55
+
56
+ have_geocoder = defined?(Geocoder)
57
+ r = Geocoder.search(string) rescue nil
58
+ @already_geocoded = true
59
+
60
+ if not r or r.length == 0
61
+ self.errors.add(:address, :did_not_geocode, message: "No locations found") if have_geocoder
62
+ self.line1 = string
63
+ else
64
+ res = r[0]
65
+ self.update_lat_lon(res)
66
+ if res.respond_to?(:street)
67
+ self.line1 = (res.house_number ? res.house_number + " " : "") + res.street
68
+ self.line2 = nil
69
+ c = Carmen::Country.coded(res.country_code) rescue nil
70
+ s = c.subregions.named(res.state) if c
71
+ self.state = s.code.to_s if s
72
+ else
73
+ self.line1 = res.street_address.to_s rescue string
74
+ self.line2 = nil
75
+ self.state = res.state_code.to_s
76
+ end
77
+ self.city = res.city.to_s
78
+ self.postal = res.postal_code.to_s
79
+ self.country = res.country_code.to_s.upcase
80
+ end
81
+
82
+ self.errors.add(:address, :too_many_matches, message: "Too many matches") if have_geocoder && r && r.length > 1
83
+ self
84
+ end
85
+
86
+ def self.find_by_address(string)
87
+ template = self.parse(string)
88
+ self.find_by(template.attributes.symbolize_keys!.slice(:line1, :line2, :city, :state))
89
+ end
90
+
91
+ def as_json(options={})
92
+ for_user = options.delete(:for_user)
93
+ res = super(options)
94
+ res[:address] = oneline
95
+ res[:label] = self.label.value if self.label
96
+ res
97
+ end
98
+
99
+ def update_from_postal
100
+ return if @already_geocoded or self.postal.blank?
101
+ r = Geocoder.search(self.postal) rescue nil
102
+ if not r or r.length == 0
103
+ return
104
+ self.errors.add(:postal, :cannot_locate_postal_code, message: "Couldn't locate postal code")
105
+ else
106
+ res = r[0]
107
+ self.update_lat_lon(res)
108
+ self.city = res.city
109
+ self.state = res.state_code
110
+ self.country = res.country_code
111
+ end
112
+ end
113
+
114
+ def oneline(options={})
115
+ if options[:full]
116
+ return [line1, line2, city, state, postal].compact.join(", ").gsub(/, ,/, ",")
117
+ else
118
+ return "#{line1}, #{city}, #{state}"
119
+ end
120
+ end
121
+
122
+ def self.parse(string)
123
+ a = self.new
124
+ a.address = string
125
+ a
126
+ end
127
+
128
+ def self.new_from_hash(incoming_hash)
129
+ p = {}
130
+ incoming_hash.each {|key, val| p[key] = val if not val.blank?}
131
+ a = self.new
132
+ a.line1 = p[:line1] || p[:address]
133
+ a.line2 = p[:line2]
134
+ a.city = p[:city]
135
+ a.state = p[:state]
136
+ a.country = p[:country]
137
+ a.postal = p[:postal] || p[:zipcode] || p[:zip]
138
+ a.label = Label.get("Work")
139
+ a
140
+ end
141
+
142
+ def self.find_or_create_by_example(other)
143
+ res = self
144
+ res = res.where(:line1 => other.line1) if other.line1
145
+ res = res.where(:line2 => other.line2) if other.line2
146
+ res = res.where(:city => other.city) if other.city
147
+ res = res.where(:state => other.state) if other.state
148
+ res = res.where(:country => other.country) if other.country
149
+ res = res.where(:postal => other.postal) if other.postal
150
+
151
+ a = nil
152
+ addresses = res
153
+ if addresses != self
154
+ a = Address.create(other.attributes.symbolize_keys!.except(:id, :created_at, :updated_at)) if addresses == []
155
+ a ||= addresses[0]
156
+ end
157
+
158
+ a
159
+ end
160
+
161
+ def admin_object_name
162
+ [line1, city, postal].join(" ").strip rescue ""
163
+ end
164
+
165
+ def person
166
+ self.people.first rescue nil
167
+ end
168
+ end
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,19 @@
1
+ class Email < ApplicationRecord
2
+ belongs_to :label, optional: true
3
+ belongs_to :emailable, polymorphic: true
4
+ accepts_nested_attributes_for :label
5
+ before_create :default_label
6
+
7
+ def self.canonicalize(addr)
8
+ addr.strip.downcase if not addr.blank?
9
+ end
10
+
11
+ def canonicalize
12
+ self.address = self.class.canonicalize(self.address)
13
+ end
14
+
15
+ def default_label
16
+ self.label = Label.get("Work")
17
+ end
18
+
19
+ end
@@ -0,0 +1,11 @@
1
+ class Label < ApplicationRecord
2
+
3
+ def self.instantiate_defaults
4
+ %w(Home Work Cell).each {|v| self.get(v)}
5
+ end
6
+
7
+ def self.get(name, lang="en")
8
+ self.where(value: name, lang: lang).first_or_create
9
+ end
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ class Nickname < ApplicationRecord
2
+ belongs_to :nicknameable, polymorphic: true
3
+ end
@@ -0,0 +1,12 @@
1
+ class Organization < ApplicationRecord
2
+ has_many :nicknames, as: :nicknameable
3
+ accepts_nested_attributes_for :nicknames
4
+ has_and_belongs_to_many :users
5
+ accepts_nested_attributes_for :users
6
+
7
+ def self.lookup(thing)
8
+ candidate = self.where(name: thing).first
9
+ candidate ||= self.includes(:nicknames).joins(:nicknames).find_by("nicknames.nickname" => thing)
10
+ end
11
+
12
+ end
@@ -0,0 +1,244 @@
1
+ class Person < ApplicationRecord
2
+ has_many :users
3
+ has_many :nicknames, as: :nicknameable
4
+ has_many :addresses, as: :addressable
5
+ has_many :phones, as: :phoneable
6
+ has_many :emails, as: :emailable
7
+
8
+ accepts_nested_attributes_for :nicknames
9
+ accepts_nested_attributes_for :addresses
10
+ accepts_nested_attributes_for :phones
11
+ accepts_nested_attributes_for :emails
12
+
13
+ def self.lookup(name)
14
+ person = self.find_by(self.name_components(name))
15
+ person ||= self.includes(:nicknames).joins(:nicknames).find_by("nicknames.nickname" => name)
16
+ 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
+ people.each do |parry|
21
+ if parry[1] == name.upcase
22
+ person = self.find(parry[0])
23
+ break
24
+ end
25
+ end
26
+ end
27
+
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
+ person
34
+ end
35
+
36
+ def user
37
+ self.users.first
38
+ end
39
+
40
+ def user=(u)
41
+ self.users = [u]
42
+ end
43
+
44
+ def email
45
+ self.emails.first
46
+ end
47
+
48
+ def email=(address)
49
+ e = self.emails.where(address: Email.canonicalize(address)).first_or_initialize() if not address.blank?
50
+ self.emails << e if not emails.include?(e)
51
+ e
52
+ end
53
+
54
+ def phone
55
+ p = self.phones.find_by(label: Label.get("Work"))
56
+ p ||= self.phones.first
57
+ p
58
+ end
59
+
60
+ def phone=(number)
61
+ if number.is_a?(Phone)
62
+ self.phones << number unless self.phones.include?(number)
63
+ return
64
+ end
65
+
66
+ if not number.blank?
67
+ p = self.phones.first rescue nil
68
+ p ||= Phone.create(phoneable_type: self.class.name, label: Label.get("Work"))
69
+ self.phones << p unless self.phones.include?(p)
70
+ p.number = number
71
+ p.save
72
+ end
73
+ end
74
+
75
+ def self.is_name_prefix?(text)
76
+ possibles = %w(mr mrs ms miss dr professor prof)
77
+ result = possibles.include?(text.gsub(/[.]*/, "").downcase) if text.present?
78
+ end
79
+
80
+ def self.is_name_suffix?(text)
81
+ possibles = %w(esq phd jr iii ii)
82
+ result = possibles.include?(text.gsub(/[.]*/, "").downcase) if text.present?
83
+ end
84
+
85
+ def self.name_components(name)
86
+ res = {}
87
+ component = ""
88
+ components = name.gsub(/,/, " ").gsub(/ /, " ").split(" ") rescue [name]
89
+ num_parts = components.length
90
+ component = components.shift
91
+
92
+ # What kind of thing is this?
93
+ if is_name_prefix?(component)
94
+ res[:prefix] = component
95
+ res[:fname] = components.shift
96
+ else
97
+ res[:fname] = component
98
+ end
99
+
100
+ # Next up, middle initial or last name.
101
+ # If only one word remains, that's the last name
102
+ if components.length == 1
103
+ res[:lname] = components.shift
104
+ elsif components.length > 0
105
+ # At least 2 words remain. We might have middle names, prefixes, suffixes, etc.
106
+ components.reverse!
107
+ component = components.shift
108
+ if is_name_suffix?(component)
109
+ res[:suffix] = component
110
+ res[:lname] = components.shift
111
+ else
112
+ res[:lname] = component
113
+ end
114
+ res[:minitial] = components.shift
115
+ end
116
+
117
+ res[:minitial] = res[:minitial].gsub(/[.]/, "") if res[:minitial].present?
118
+ res
119
+ end
120
+
121
+ def name
122
+ components = []
123
+
124
+ if prefix.present?
125
+ if not prefix.include?(".")
126
+ components << "#{prefix}."
127
+ else
128
+ components << prefix
129
+ end
130
+ end
131
+
132
+ components << self.fname if self.fname.present?
133
+
134
+ if minitial.present?
135
+ if minitial.length < 2
136
+ components << "#{minitial}."
137
+ else
138
+ components << minitial
139
+ end
140
+ end
141
+
142
+ components << self.lname if self.lname.present?
143
+ components << ", #{suffix}" if self.suffix.present?
144
+ components.join(" ").strip.gsub(/ ,/, ",")
145
+ end
146
+
147
+ def name=(incoming_name)
148
+ components = self.class.name_components(incoming_name)
149
+ self.prefix = components[:prefix]
150
+ self.fname = components[:fname]
151
+ self.minitial = components[:minitial]
152
+ self.lname = components[:lname]
153
+ self.suffix = components[:suffix]
154
+ end
155
+
156
+ def age
157
+ ((Date.today - self.birthdate).to_i / 365) if self.birthdate.present?
158
+ end
159
+
160
+ def age=(new_age)
161
+ self.birthdate = (Date.today - (rand(Date.today.month - 1))) - new_age.years
162
+ self.birthdate
163
+ end
164
+
165
+ def address
166
+ a = self.addresses.where(label: Label.get("Work")).first
167
+ a ||= self.addresses.first
168
+ end
169
+
170
+ def address=(new_address)
171
+ candidate = nil
172
+ if new_address.is_a?(Address)
173
+ candidate = new_address
174
+ elsif %w(Integer Fixnum Bignum).include?(new_address.class.to_s)
175
+ candidate = Address.find(new_address)
176
+ else
177
+ parsed = Address.parse(new_address)
178
+ candidate = self.addresses.where(line1: parsed.line1).first
179
+ candidate ||= Address.new
180
+ parsed.attributes.each {|key, val| candidate.send((key + "=").to_sym, val) unless val == nil }
181
+ candidate.save
182
+ end
183
+ self.addresses << candidate unless self.addresses.include?(candidate)
184
+ self.save
185
+ end
186
+
187
+ def self.find_or_create_by(hash)
188
+ options = hash.clone
189
+ if options.has_key?(:name)
190
+ options = options.merge(self.name_components(options[:name]))
191
+ options.delete(:name)
192
+ end
193
+ super(options)
194
+ end
195
+
196
+ def self.find_by_nickname(nick)
197
+ person = self.includes(:nicknames).joins(:nicknames).find_by("nicknames.nickname" => nick)
198
+ end
199
+
200
+ def self.find_by_email(addr)
201
+ e = Email.where(address: Email.canonicalize(addr), emailable_type: self.name)
202
+ e.person if e
203
+ end
204
+
205
+ def self.find_by_phone_number(number)
206
+ p = Phone.where(number: Phone.canonicalize(number), phoneable_type: self.name)
207
+ p.person if p
208
+ end
209
+
210
+ def self.find_by_address(string)
211
+ a = Address.find_by_address(string)
212
+ a.person if a and a.addressable_type == self.name
213
+ end
214
+
215
+ def add_phone(number, label="Home")
216
+ if self != self.class.find_by_phone_number(number)
217
+ p = Phone.create(number: number, phoneable_type: self.class.name, label: Label.get(label))
218
+ phones << p
219
+ end
220
+ p = Phone.where(number: Phone.canonicalize(number), phoneable_type: self.class.name)
221
+ end
222
+
223
+ def add_email(address, label="Home")
224
+ address = Email.canonicalize(address)
225
+ if self != self.class.find_by_email(address)
226
+ e = Email.create(address: address, emailable_type: self.class.name, label: Label.get(label))
227
+ emails << e
228
+ end
229
+ e = Email.where(address:address, emailable_type: self.class.name)
230
+ end
231
+
232
+ def add_address(address_line, label="Home")
233
+ if self != self.class.find_by_address(address_line)
234
+ a = Address.parse(address_line);
235
+ a.addressable_type = self.class.name;
236
+ a.label = Label.get(label)
237
+ a.save
238
+ addresses << a
239
+ end
240
+ a = Address.find_by_address(address_line)
241
+ end
242
+
243
+
244
+ end
@@ -0,0 +1,26 @@
1
+ class Phone < ApplicationRecord
2
+ belongs_to :label, optional: true
3
+ belongs_to :phoneable, polymorphic: true
4
+ before_validation :canonicalize
5
+
6
+ def self.canonicalize(digits_and_stuff)
7
+ canonical = digits_and_stuff
8
+ canonical.gsub!(" ", "") #remove extra spaces
9
+ if canonical
10
+ 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]}"
16
+ end
17
+ end
18
+ canonical
19
+ end
20
+
21
+ def canonicalize
22
+ return if self.number == "76-BAFFLE-76"
23
+ self.number = self.class.canonicalize(self.number) if self.number
24
+ end
25
+
26
+ end
@@ -0,0 +1,32 @@
1
+ class User < ApplicationRecord
2
+ belongs_to :person
3
+ accepts_nested_attributes_for :person
4
+ has_many :credentials, as: :credentialed, dependent: :destroy
5
+ accepts_nested_attributes_for :credentials
6
+ has_and_belongs_to_many :organizations
7
+
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
19
+
20
+ def self.lookup(item)
21
+ person = Person.lookup(item)
22
+ (person.send self.name.downcase.to_sym) if person
23
+ end
24
+
25
+ def organization=(org)
26
+ self.organizations << org if not self.organizations.include?(org)
27
+ end
28
+
29
+ def organization
30
+ self.organizations.order(created_at: :desc).first
31
+ end
32
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,10 @@
1
+ class CreateLabels < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :labels do |t|
4
+ t.string :value
5
+ t.string :lang
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ class CreateNicknames < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :nicknames do |t|
4
+ t.string :nickname
5
+ t.references :nicknameable, polymorphic: true
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class CreateAddresses < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :addresses do |t|
4
+ t.references :label, foreign_key: true
5
+ t.references :addressable, polymorphic: true
6
+ t.string :line1
7
+ t.string :line2
8
+ t.string :city
9
+ t.string :state
10
+ t.string :postal
11
+ t.string :country
12
+ t.float :lat
13
+ t.float :lon
14
+ t.boolean :confirmed
15
+
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ class CreatePhones < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :phones do |t|
4
+ t.references :label, foreign_key: true
5
+ t.references :phoneable, polymorphic: true
6
+ t.string :number
7
+ t.boolean :confirmed
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ class CreateEmails < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :emails do |t|
4
+ t.references :label, foreign_key: true
5
+ t.references :emailable, polymorphic: true
6
+ t.string :address
7
+ t.boolean :confirmed
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ class CreatePeople < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :people do |t|
4
+ t.string :prefix, limit: 10
5
+ t.string :fname
6
+ t.string :minitial
7
+ t.string :lname
8
+ t.string :suffix, limit: 10
9
+ t.string :birthdate
10
+ t.string :nationality
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ class CreateOrganizations < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :organizations do |t|
4
+ t.string :name
5
+ t.string :nationality
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class CreateUsers < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :users do |t|
4
+ t.references :person, foreign_key: true
5
+ t.string :status
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
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
@@ -0,0 +1,8 @@
1
+ class CreateJoinTableOrganizationsUsers < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_join_table :organizations, :users do |t|
4
+ # t.index [:organization_id, :user_id]
5
+ # t.index [:user_id, :organization_id]
6
+ end
7
+ end
8
+ end
data/db/seeds.rb ADDED
@@ -0,0 +1,8 @@
1
+ # coding: utf-8
2
+ opus = Organization.where(name: "Opus Logica, Inc").first_or_create(nicknames_attributes: [{ nickname: "OPUS" }])
3
+ bfox = User.lookup("bfox@opuslogica.com") ||
4
+ User.create(person_attributes: { email: "bfox@opuslogica.com", name: "Brian Jhan Fox",
5
+ address: "901 Olive St., Santa Barbara, CA, 93101",
6
+ phone: "805.555.8642" },
7
+ organization: opus,
8
+ credentials_attributes: [{ password: "idtmp2tv" }])
@@ -0,0 +1,9 @@
1
+ require "openopus/core/people/engine"
2
+
3
+ module Openopus
4
+ module Core
5
+ module People
6
+ # Your code goes here...
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,7 @@
1
+ module Openopus
2
+ module Core
3
+ module People
4
+ VERSION = '1.1.0'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :openopus_core_people do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openopus-core-people
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian J. Fox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.3
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.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: bcrypt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.1.13
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.1.13
41
+ description: A person can have many email addresses, but this is not usually represented
42
+ in applications. openopus/core/people creates the database structure, relations,
43
+ and convenience functions for your application so you don't have to. Just connect
44
+ your end user model, and away you go.
45
+ email:
46
+ - bfox@opuslogica.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - MIT-LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - app/assets/config/openopus_core_people_manifest.js
55
+ - app/models/address.rb
56
+ - app/models/credential.rb
57
+ - app/models/email.rb
58
+ - app/models/label.rb
59
+ - app/models/nickname.rb
60
+ - app/models/organization.rb
61
+ - app/models/person.rb
62
+ - app/models/phone.rb
63
+ - app/models/user.rb
64
+ - config/routes.rb
65
+ - db/migrate/00000000000000_create_labels.rb
66
+ - db/migrate/00000000000001_create_nicknames.rb
67
+ - db/migrate/00000000000002_create_addresses.rb
68
+ - db/migrate/00000000000003_create_phones.rb
69
+ - db/migrate/00000000000004_create_emails.rb
70
+ - db/migrate/00000000000005_create_people.rb
71
+ - db/migrate/00000000000006_create_organizations.rb
72
+ - db/migrate/00000000000007_create_users.rb
73
+ - db/migrate/00000000000008_create_credentials.rb
74
+ - db/migrate/00000000000009_create_join_table_organizations_users.rb
75
+ - db/seeds.rb
76
+ - lib/openopus/core/people.rb
77
+ - lib/openopus/core/people/engine.rb
78
+ - lib/openopus/core/people/version.rb
79
+ - lib/tasks/openopus/core/people_tasks.rake
80
+ homepage: https://github.com/opuslogica/openopus-core-people
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ allowed_push_host: https://rubygems.org
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.0.1
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Model the real world of people in your application, making user interaction
104
+ robust.
105
+ test_files: []