personhood 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +19 -0
  4. data/Gemfile.lock +111 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +279 -0
  7. data/Rakefile +55 -0
  8. data/VERSION +1 -0
  9. data/lib/juscribe/display_optional.rb +19 -0
  10. data/lib/juscribe/name.rb +42 -0
  11. data/lib/juscribe/personhood.rb +93 -0
  12. data/lib/juscribe/tos_acceptable.rb +26 -0
  13. data/lib/personhood.rb +10 -0
  14. data/spec/dummy/Rakefile +7 -0
  15. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  16. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  17. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  18. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  19. data/spec/dummy/app/mailers/.gitkeep +0 -0
  20. data/spec/dummy/app/models/.gitkeep +0 -0
  21. data/spec/dummy/app/models/user.rb +3 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  23. data/spec/dummy/config/application.rb +45 -0
  24. data/spec/dummy/config/boot.rb +10 -0
  25. data/spec/dummy/config/database.yml +25 -0
  26. data/spec/dummy/config/environment.rb +5 -0
  27. data/spec/dummy/config/environments/development.rb +30 -0
  28. data/spec/dummy/config/environments/production.rb +60 -0
  29. data/spec/dummy/config/environments/test.rb +39 -0
  30. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  31. data/spec/dummy/config/initializers/inflections.rb +10 -0
  32. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  33. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  34. data/spec/dummy/config/initializers/session_store.rb +8 -0
  35. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  36. data/spec/dummy/config/locales/en.yml +5 -0
  37. data/spec/dummy/config/routes.rb +58 -0
  38. data/spec/dummy/config.ru +4 -0
  39. data/spec/dummy/db/migrate/20121022000000_create_users.rb +19 -0
  40. data/spec/dummy/db/schema.rb +28 -0
  41. data/spec/dummy/db/test.sqlite3 +0 -0
  42. data/spec/dummy/lib/assets/.gitkeep +0 -0
  43. data/spec/dummy/log/.gitkeep +0 -0
  44. data/spec/dummy/log/development.log +20 -0
  45. data/spec/dummy/public/404.html +26 -0
  46. data/spec/dummy/public/422.html +26 -0
  47. data/spec/dummy/public/500.html +26 -0
  48. data/spec/dummy/public/favicon.ico +0 -0
  49. data/spec/dummy/script/rails +6 -0
  50. data/spec/personhood_spec.rb +237 -0
  51. data/spec/spec_helper.rb +23 -0
  52. data/spec/support/have_error_matchers.rb +46 -0
  53. metadata +202 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ # gem "rspec", "~> 2.11.0"
10
+ gem 'rspec-rails', '~> 2.11'
11
+ gem "rdoc", "~> 3.12"
12
+ gem "bundler", ">= 1.0.0"
13
+ gem "jeweler", "~> 1.8.4"
14
+ # gem "rcov", ">= 0"
15
+
16
+ # gem 'railties', '>= 3.1.0'
17
+ gem 'rails', '>= 3.1.0'
18
+ gem 'sqlite3'
19
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,111 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (3.2.8)
5
+ actionpack (= 3.2.8)
6
+ mail (~> 2.4.4)
7
+ actionpack (3.2.8)
8
+ activemodel (= 3.2.8)
9
+ activesupport (= 3.2.8)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ journey (~> 1.0.4)
13
+ rack (~> 1.4.0)
14
+ rack-cache (~> 1.2)
15
+ rack-test (~> 0.6.1)
16
+ sprockets (~> 2.1.3)
17
+ activemodel (3.2.8)
18
+ activesupport (= 3.2.8)
19
+ builder (~> 3.0.0)
20
+ activerecord (3.2.8)
21
+ activemodel (= 3.2.8)
22
+ activesupport (= 3.2.8)
23
+ arel (~> 3.0.2)
24
+ tzinfo (~> 0.3.29)
25
+ activeresource (3.2.8)
26
+ activemodel (= 3.2.8)
27
+ activesupport (= 3.2.8)
28
+ activesupport (3.2.8)
29
+ i18n (~> 0.6)
30
+ multi_json (~> 1.0)
31
+ arel (3.0.2)
32
+ builder (3.0.4)
33
+ diff-lcs (1.1.3)
34
+ erubis (2.7.0)
35
+ git (1.2.5)
36
+ hike (1.2.1)
37
+ i18n (0.6.1)
38
+ jeweler (1.8.4)
39
+ bundler (~> 1.0)
40
+ git (>= 1.2.5)
41
+ rake
42
+ rdoc
43
+ journey (1.0.4)
44
+ json (1.7.5)
45
+ mail (2.4.4)
46
+ i18n (>= 0.4.0)
47
+ mime-types (~> 1.16)
48
+ treetop (~> 1.4.8)
49
+ mime-types (1.19)
50
+ multi_json (1.3.6)
51
+ polyglot (0.3.3)
52
+ rack (1.4.1)
53
+ rack-cache (1.2)
54
+ rack (>= 0.4)
55
+ rack-ssl (1.3.2)
56
+ rack
57
+ rack-test (0.6.2)
58
+ rack (>= 1.0)
59
+ rails (3.2.8)
60
+ actionmailer (= 3.2.8)
61
+ actionpack (= 3.2.8)
62
+ activerecord (= 3.2.8)
63
+ activeresource (= 3.2.8)
64
+ activesupport (= 3.2.8)
65
+ bundler (~> 1.0)
66
+ railties (= 3.2.8)
67
+ railties (3.2.8)
68
+ actionpack (= 3.2.8)
69
+ activesupport (= 3.2.8)
70
+ rack-ssl (~> 1.3.2)
71
+ rake (>= 0.8.7)
72
+ rdoc (~> 3.4)
73
+ thor (>= 0.14.6, < 2.0)
74
+ rake (0.9.2.2)
75
+ rdoc (3.12)
76
+ json (~> 1.4)
77
+ rspec (2.11.0)
78
+ rspec-core (~> 2.11.0)
79
+ rspec-expectations (~> 2.11.0)
80
+ rspec-mocks (~> 2.11.0)
81
+ rspec-core (2.11.1)
82
+ rspec-expectations (2.11.3)
83
+ diff-lcs (~> 1.1.3)
84
+ rspec-mocks (2.11.3)
85
+ rspec-rails (2.11.4)
86
+ actionpack (>= 3.0)
87
+ activesupport (>= 3.0)
88
+ railties (>= 3.0)
89
+ rspec (~> 2.11.0)
90
+ sprockets (2.1.3)
91
+ hike (~> 1.2)
92
+ rack (~> 1.0)
93
+ tilt (~> 1.1, != 1.3.0)
94
+ sqlite3 (1.3.6)
95
+ thor (0.16.0)
96
+ tilt (1.3.3)
97
+ treetop (1.4.11)
98
+ polyglot
99
+ polyglot (>= 0.3.1)
100
+ tzinfo (0.3.33)
101
+
102
+ PLATFORMS
103
+ ruby
104
+
105
+ DEPENDENCIES
106
+ bundler (>= 1.0.0)
107
+ jeweler (~> 1.8.4)
108
+ rails (>= 3.1.0)
109
+ rdoc (~> 3.12)
110
+ rspec-rails (~> 2.11)
111
+ sqlite3
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 caleon
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.rdoc ADDED
@@ -0,0 +1,279 @@
1
+ = Personhood
2
+
3
+ When you are tired of coding the same kinds of things for your User model (or any other person-like model) with all its typical +first_name+, +full_name+, and other brouhaha, use the Personhood gem to clean up your code by getting rid of those pesky lines and instead focusing on the lines of code that *truly* set your app apart.
4
+
5
+ This is a bit of an opinionated library, so the following assumptions are in place (although in the future this can be made more customizable):
6
+
7
+ == Assumptions
8
+
9
+ 1. Your class is expected to have the following attributes:
10
+
11
+ * email (string)
12
+ * username (string)
13
+ * first_name (string)
14
+ * middle_name (string)
15
+ * last_name (string)
16
+ * birthdate (date)
17
+ * sex (integer)
18
+
19
+ 2. All attributes listed above are +attr_accessible+ as +:admin+.
20
+
21
+ 3. All but the first two attributes (email/username) listed above are +attr_accessible+ under normal cases.
22
+
23
+ 4. Out of the box, +last_name+ and +first_name+ are required. They, along with +middle_name+ are allowed at most 255 characters.
24
+
25
+ 5. As a +before_save+ callback, whitespaces are stripped from each of +last_name+, +first_name+, and +middle_name+.
26
+
27
+ 6. A record without a +birthdate+ attribute set (or nil) is treated as one whose birthdate is today, hence +age+ would be 0.
28
+
29
+ == Customizations
30
+
31
+ Currently the only customization at the API level is with regard to overriding the +#to_param+ instance method. Under normal circumstances you would override this to be used as a URL slug (permalinking with strings instead of ID numerics), but the library goes further to set the return value of +#to_s+ to the value of +#to_param+.
32
+
33
+ Typically, I would suggest you defining your custom +#to_param+ within your model, but if you need to customize +#to_param+ for your own needs but do not wish to have its value associated with +#to_s+, you can override that method instead.
34
+
35
+ == Usage
36
+
37
+ === Installation
38
+
39
+ Gemfile:
40
+
41
+ gem 'personhood', github: 'caleon/personhood'
42
+
43
+ Model class file (i.e. +app/models/user.rb+):
44
+
45
+ class User
46
+ include Juscribe::Personhood
47
+ # ..
48
+ end
49
+
50
+ The include statement does not necessarily have to be at the very top of the class definition, but just be aware that upon inclusion, the module also writes a +composed_of+ statement, +validates+ defaults, and the aforementioned +attr_accessible+ rules.
51
+
52
+ === API
53
+
54
+ Once your model is properly set up, these are the methods available to you:
55
+
56
+ # Given:
57
+ user = User.new({
58
+ email: 'john@doe.com',
59
+ username: 'johnDoe'
60
+ first_name: 'john',
61
+ last_name: 'doe',
62
+ middle_name: 'quincy',
63
+ sex: 1,
64
+ birthdate: Date.new(1970, 1, 1)
65
+ }, as: :admin)
66
+
67
+ Getting back on track, here are the _basic_ methods:
68
+
69
+ user.to_param
70
+ # => "johnDoe"
71
+
72
+ user.to_s
73
+ # => "johnDoe"
74
+
75
+ user.full_name
76
+ # => "john q. doe"
77
+
78
+ user.first_and_last_name
79
+ # => "john doe"
80
+
81
+ user.email_address
82
+ # => "john doe <john@doe.com>"
83
+
84
+ user.sex
85
+ # => "m"
86
+
87
+ user.sex(:full)
88
+ # => "male"
89
+
90
+ user.male?
91
+ # => true
92
+
93
+ user.female?
94
+ # => false
95
+
96
+ user.androgynous?
97
+ # => false
98
+
99
+ # Indeterminate sex:
100
+ andro = User.new
101
+
102
+ andro.sex
103
+ # => "?"
104
+
105
+ andro.sex(:full)
106
+ # => "(unknown)"
107
+
108
+ andro.androgynous?
109
+ # => true
110
+
111
+ user.age
112
+ # returns whatever the age happens to be for someone born on 1990/1/1
113
+
114
+ andro.age
115
+ # => 0
116
+
117
+ The following are available due to the underlying Juscribe::Name method. You are free to interact with the Juscribe::Name class if you'd like, but the intent is to be hidden from your code and interacted only via the Juscribe::Personhood module which gets included in your class.
118
+
119
+ user.name
120
+ # => "john quincy doe"
121
+
122
+ user.name.class
123
+ # => Juscribe::Name
124
+
125
+ user.name.to_s
126
+ # => "john q. doe"
127
+
128
+ user.name.first
129
+ # => "john"
130
+
131
+ user.name.middle
132
+ # => "quincy"
133
+
134
+ user.name.middle_initial
135
+ # => "q."
136
+
137
+ user.name.last
138
+ # => "doe"
139
+
140
+ user.name.full
141
+ # => "john q. doe"
142
+
143
+ user.name.complete
144
+ # => "john quincy doe"
145
+
146
+ The other byproduct of using this intermediary class is that it allows usage of +ActiveRecord+'s +composed_of+ statement:
147
+
148
+ user = User.create(name: 'Jane Zeta Doe') do |u|
149
+ # ..
150
+ # Assume other required attributes are set here
151
+ # ..
152
+ end
153
+
154
+ user.first_name
155
+ # => "Jane"
156
+
157
+ user.middle_name
158
+ # => "Zeta"
159
+
160
+ user.middle_initial
161
+ # => "Z."
162
+
163
+ user.last_name
164
+ # => "Doe"
165
+
166
+ name = Juscribe::Name.new('Jane', 'Zeta', 'Doe')
167
+ # or
168
+ name = Juscribe::Name.convert('Jane Zeta Doe')
169
+
170
+ User.where(name: name)
171
+ # User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`first_name` = 'Jane' AND `users`.`middle_name` = 'Zeta' AND `users`.`last_name` = 'Doe'
172
+ # => [#<User id: ..>]
173
+
174
+ # Note: this means all mapped name parts must match exactly:
175
+ name = Juscribe::Name.convert('Jane Doe')
176
+ User.where(name: name)
177
+ # User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`first_name` = 'Jane' AND `users`.`middle_name` IS NULL AND `users`.`last_name` = 'Doe'
178
+ # => []
179
+
180
+ If you see yourself using Juscribe::Name a lot, you might want to consider aliasing it at the root level with:
181
+
182
+ Name = Juscribe::Name
183
+
184
+ === Sidenote
185
+
186
+ Examples above were only using the +:admin+ role for demonstration. Ideally such account-level attributes should undergo more strenuous security checks in your controller. A simple way would be:
187
+
188
+ user = User.new(params[:user]) do |u|
189
+ u.email = params[:user][:email]
190
+ u.username = params[:user][:username]
191
+ end
192
+
193
+ Values for +#sex+ are left lowercased because I'd argue capitalization should be handled at the CSS level with something like:
194
+
195
+ .user .sex { text-transform: capitalize; }
196
+
197
+ === Other modules included as a dependency
198
+
199
+ ==== Juscribe::Name
200
+
201
+ The usage of this class is demonstrated above and its purpose is for abstraction and integration with +composed_of+.
202
+
203
+ ==== Juscribe::DisplayOptional
204
+
205
+ This is used in the inner-workings of +Personhood+ to display a default "unknown" string if the actual value is blank.
206
+
207
+ ==== Juscribe::TosAcceptable
208
+
209
+ This module is included in the gem now, but I'm considering taking it out, as it applies less to the "personhood" of an object but more to its "user-account-ness". For now, you may go on to use it in the following way:
210
+
211
+ # within app/models/user.rb
212
+ include Juscribe::TosAcceptable
213
+
214
+ # within migration
215
+ add_column :users, :tos_accepted_at, :datetime
216
+
217
+ # within your registration form
218
+ <%= form_for User.new do |f| %>
219
+ <%#= .. other form stuff %>
220
+
221
+ <%= f.label :tos_accepted do %>
222
+ <%= f.check_box :tos_accepted, value: '1' %>
223
+ I agree to the <%= link_to 'Terms of Use', terms_path %>.
224
+ <% end %>
225
+
226
+ <%= f.submit %>
227
+ <% end %>
228
+
229
+ In the background, there is a faux-attribute-accessor for +:tos_accepted+ which, when asked to write a truthy form value, writes the timestamp when the form was submitted.
230
+
231
+ Note that by including the Juscribe::TosAcceptable you are also adding the acceptance validation on that field (existing records without that field set will not validate).
232
+
233
+ == Roadmap
234
+
235
+ === rex_validate
236
+
237
+ If you looked in the source code you will find references to regexp validation commented out. As this gem was lifted from my other projects which typically use the rex_validate gem, I had to take out that aspect of the integration for the time being while I work out a cleaner way to integrate the two.
238
+
239
+ === More convenient Query parameters
240
+
241
+ As in: "User.where(name: 'John Doe')"" instead of instantiating a new object of the Juscribe::Name class.
242
+
243
+ === Customizable column names
244
+
245
+ Shouldn't be hard to do, and the aim is to not do +include Juscribe::Personhood+ and instead do:
246
+
247
+ class User < ActiveRecord::Base
248
+ claims_personhood columns: {
249
+ first_name: :name1,
250
+ # ..
251
+ birthdate: :dob
252
+ },
253
+ defaults: {
254
+ birthdate: Date.new(1900, 1, 1),
255
+ age: nil
256
+ }
257
+ end
258
+
259
+ ... although it might be simpler to +alias_attribute+ and override the +birthdate+ or +age+ methods in those options.
260
+
261
+ === General Direction
262
+
263
+ While it might occur to some to include as options basic groups of functionality involving things like a person's address or profile information, the initial intent of the Personhood library was to contain just the bare minimum commonalities among typical projects. Furthermore, it should apply to non-user-related classes like +President+, for instance, which contains in its table a listing of all the presidents. In such a case, things like even +email_address+ are irrelevant and should not really apply. More fitting, perhaps, are extensions involving things like a person's biometrics (eye color, height, weight), birthplace or ethnicity.
264
+
265
+ == Contributing to Personhood
266
+
267
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
268
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
269
+ * Fork the project.
270
+ * Start a feature/bugfix branch.
271
+ * Commit and push until you are happy with your contribution.
272
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
273
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
274
+
275
+ == Copyright
276
+
277
+ Copyright (c) 2012 caleon. See LICENSE.txt for
278
+ further details.
279
+
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "personhood"
18
+ gem.homepage = "http://github.com/caleon/personhood"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{ActiveRecord abstraction for attributes pertaining to a person-like model}
21
+ gem.description = <<-DESC
22
+ When you are tired of coding the same kinds of things for your User model
23
+ (or any other person-like model) with all its typical `first_name`,
24
+ `full_name`, and other brouhaha, use the Personhood gem to clean up your
25
+ code by getting rid of those pesky lines and instead focusing on the lines of
26
+ code that *truly* set your app apart.
27
+ DESC
28
+ gem.email = "caleon@gmail.com"
29
+ gem.authors = ["caleon"]
30
+ # dependencies defined in Gemfile
31
+ end
32
+ Jeweler::RubygemsDotOrgTasks.new
33
+
34
+ require 'rspec/core'
35
+ require 'rspec/core/rake_task'
36
+ RSpec::Core::RakeTask.new(:spec) do |spec|
37
+ spec.pattern = FileList['spec/**/*_spec.rb']
38
+ end
39
+
40
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
41
+ spec.pattern = 'spec/**/*_spec.rb'
42
+ spec.rcov = true
43
+ end
44
+
45
+ task :default => :spec
46
+
47
+ require 'rdoc/task'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "Personhood #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
@@ -0,0 +1,19 @@
1
+ module Juscribe # :nodoc:
2
+ module DisplayOptional
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :unknown_label
7
+ self.unknown_label = 'unnamed'
8
+ end
9
+
10
+ def to_s
11
+ display_optional(to_param)
12
+ end
13
+
14
+ private
15
+ def display_optional(value)
16
+ value.blank? ? "(#{unknown_label})" : value
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,42 @@
1
+ module Juscribe
2
+ class Name
3
+ attr_reader :first, :middle, :last
4
+
5
+ class << self
6
+ def convert(input)
7
+ case input
8
+ when String
9
+ new(*input.split(/\s+/))
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(first, *others, last)
15
+ @first, @middle, @last = first, others.first, last
16
+ end
17
+
18
+ def middle_initial
19
+ "#{middle.first}." if middle.present?
20
+ end
21
+
22
+ def first_and_last
23
+ [first, *last].join(' ')
24
+ end
25
+
26
+ def full
27
+ [first, *middle_initial, *last].join(' ')
28
+ end
29
+
30
+ def complete
31
+ [first, *middle, *last].join(' ')
32
+ end
33
+
34
+ def to_s
35
+ full
36
+ end
37
+
38
+ def inspect
39
+ %Q{"#{complete}"}
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,93 @@
1
+ module Juscribe # :nodoc:
2
+ module Personhood
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ composed_of :name, class_name: 'Juscribe::Name',
7
+ converter: :convert,
8
+ mapping: [%w(first_name first),
9
+ %w(middle_name middle),
10
+ %w(last_name last)]
11
+
12
+ # rex_validate :last_name, :first_name, :middle_name
13
+ with_options length: { maximum: 255 } do |x|
14
+ x.validates :last_name, :first_name, presence: true
15
+ x.validates :middle_name
16
+ end
17
+
18
+ attr_accessible :last_name, :first_name, :middle_name, :name, :birthdate, :sex
19
+ attr_accessible :last_name, :first_name, :middle_name, :name, :birthdate, :sex, :email, :username, as: :admin
20
+ end
21
+
22
+ # ----------------------------------------
23
+ # :section: Basic Info
24
+ # ----------------------------------------
25
+
26
+ # Override the following to control what gets displayed on #to_s
27
+ def to_param
28
+ username
29
+ end
30
+
31
+ def middle_initial
32
+ display_optional name.middle_initial
33
+ end
34
+
35
+ ##
36
+ # Full name, unless empty, then shows "unnamed" variant.
37
+ def full_name
38
+ display_optional name.full
39
+ end
40
+
41
+ # Same as #full_name but omitting the middle_name.
42
+ def first_and_last_name
43
+ display_optional name.first_and_last
44
+ end
45
+
46
+ # Email, when the db field is nil, still returns an empty string,
47
+ # I suppose for Devise's sake.
48
+ def email_address
49
+ "#{first_and_last_name} <#{email}>" if email.present?
50
+ end
51
+
52
+ # Defaults to <tt>Date.today</tt> if +nil+.
53
+ def birthdate
54
+ read_attribute(:birthdate) || Date.today
55
+ end
56
+
57
+ def age
58
+ today, bday = Date.today, birthdate
59
+ years = today.year - bday.year
60
+ years.tap { |yrs| yrs -= 1 if (bday + years.years) > today }
61
+ # is this logic even solid?
62
+ end
63
+
64
+ def sex(format = nil)
65
+ full_int = format == :full ? 1 : 0
66
+ if read_attribute(:sex)
67
+ %w(female male)[self[:sex]][0..full_int.send(:-@)]
68
+ else
69
+ %w{? (unknown)}[full_int]
70
+ end
71
+ end
72
+
73
+ def male?
74
+ sex == 'm'
75
+ end
76
+
77
+ def female?
78
+ sex == 'f'
79
+ end
80
+
81
+ def androgynous?
82
+ read_attribute(:sex).nil?
83
+ end
84
+
85
+ private
86
+ def strip_names!
87
+ %w(first_name middle_name last_name).each do |name_part|
88
+ send(name_part).strip! if send(:"#{name_part}_changed?")
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,26 @@
1
+ module Juscribe # :nodoc:
2
+ module TosAcceptable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ validates :terms_of_service, acceptance: { accept: true }
7
+
8
+ attr_accessor :tos_accepted, :tos_accepted_at
9
+ end
10
+
11
+ def tos_accepted=(val)
12
+ self.tos_accepted_at = Time.now
13
+ end
14
+
15
+ def tos_accepted_at=(timestamp)
16
+ write_attribute(:tos_accepted_at, timestamp) if has_attribute?(:tos_accepted_at)
17
+ @_tos_accepted_at = timestamp
18
+ end
19
+
20
+ def tos_accepted?
21
+ @_tos_accepted_at.try(:<, Time.now)
22
+ end
23
+ alias_method :tos_accepted, :tos_accepted?
24
+ alias_method :terms_of_service, :tos_accepted?
25
+ end
26
+ end
data/lib/personhood.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'active_record'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'juscribe/display_optional'
6
+ require 'juscribe/name'
7
+ require 'juscribe/personhood'
8
+ require 'juscribe/tos_acceptable'
9
+
10
+ ActiveRecord::Base.send(:include, Juscribe::DisplayOptional)