radiant-reader-extension 3.0.0.rc4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/README.md +105 -38
  2. data/Rakefile +4 -4
  3. data/app/controllers/accounts_controller.rb +24 -11
  4. data/app/controllers/admin/groups_controller.rb +15 -0
  5. data/app/controllers/admin/memberships_controller.rb +13 -7
  6. data/app/controllers/admin/messages_controller.rb +1 -0
  7. data/app/controllers/admin/permissions_controller.rb +15 -8
  8. data/app/controllers/groups_controller.rb +6 -4
  9. data/app/controllers/password_resets_controller.rb +1 -1
  10. data/app/controllers/reader_action_controller.rb +7 -51
  11. data/app/controllers/reader_sessions_controller.rb +9 -11
  12. data/app/helpers/reader_helper.rb +23 -3
  13. data/app/models/group.rb +45 -16
  14. data/app/models/message.rb +4 -11
  15. data/app/models/permission.rb +4 -0
  16. data/app/models/reader.rb +61 -26
  17. data/app/models/reader_notifier.rb +2 -2
  18. data/app/models/reader_page.rb +9 -5
  19. data/app/views/accounts/{_memberships.html.haml → _choose_memberships.html.haml} +0 -0
  20. data/app/views/accounts/_form.html.haml +9 -11
  21. data/app/views/accounts/_preamble.html.haml +14 -0
  22. data/app/views/accounts/_profile_form.html.haml +13 -29
  23. data/app/views/accounts/dashboard.html.haml +0 -1
  24. data/app/views/accounts/edit.html.haml +3 -3
  25. data/app/views/accounts/edit_profile.html.haml +10 -10
  26. data/app/views/admin/groups/_chooser.html.haml +8 -0
  27. data/app/views/admin/groups/_form.html.haml +7 -5
  28. data/app/views/admin/groups/_group.html.haml +29 -0
  29. data/app/views/admin/groups/index.html.haml +2 -28
  30. data/app/views/admin/groups/show.html.haml +3 -2
  31. data/app/views/admin/memberships/_reader.html.haml +2 -6
  32. data/app/views/admin/messages/_form.html.haml +10 -11
  33. data/app/views/admin/messages/_list_function.haml +2 -3
  34. data/app/views/admin/messages/{index.haml → index.html.haml} +21 -11
  35. data/app/views/admin/pages/_page_groups.html.haml +5 -11
  36. data/app/views/admin/permissions/_page.html.haml +6 -12
  37. data/app/views/admin/reader_configuration/edit.html.haml +2 -0
  38. data/app/views/admin/reader_configuration/show.html.haml +2 -0
  39. data/app/views/groups/_group.html.haml +12 -0
  40. data/app/views/groups/show.html.haml +10 -3
  41. data/app/views/readers/_description.html.haml +3 -0
  42. data/app/views/readers/_groups.html.haml +9 -0
  43. data/app/views/{accounts → readers}/_links.html.haml +2 -2
  44. data/app/views/readers/_list.html.haml +23 -0
  45. data/app/views/{accounts/_groups.html.haml → readers/_memberships.html.haml} +0 -1
  46. data/app/views/{accounts/_reader.html.haml → readers/_mugshot.html.haml} +1 -1
  47. data/app/views/readers/_people.html.haml +6 -0
  48. data/app/views/readers/_profile.html.haml +30 -0
  49. data/app/views/readers/_reader.html.haml +28 -0
  50. data/app/views/readers/index.html.haml +20 -0
  51. data/app/views/{accounts → readers}/show.html.haml +1 -2
  52. data/app/views/shared/_standard_reader_parts.html.haml +1 -1
  53. data/app/views/shared/not_allowed.html.haml +16 -0
  54. data/config/initializers/formats.rb +1 -2
  55. data/config/initializers/radiant_config.rb +5 -2
  56. data/config/locales/en.yml +181 -140
  57. data/config/routes.rb +25 -16
  58. data/db/migrate/001_create_readers.rb +0 -1
  59. data/db/migrate/20090921125653_reader_messages.rb +0 -1
  60. data/db/migrate/20090921125654_group_messages.rb +0 -1
  61. data/db/migrate/20110812111934_groups_nested_set.rb +19 -0
  62. data/db/migrate/20110814070858_message_has_many_groups.rb +14 -0
  63. data/db/migrate/20110905194602_group_ancestry.rb +23 -0
  64. data/lib/controller_extensions.rb +49 -0
  65. data/lib/grouped_model.rb +49 -8
  66. data/lib/grouped_page.rb +17 -5
  67. data/lib/message_tags.rb +21 -3
  68. data/lib/radiant-reader-extension.rb +1 -1
  69. data/lib/reader_admin_ui.rb +7 -8
  70. data/lib/reader_tags.rb +1 -1
  71. data/lib/site_controller_extensions.rb +7 -18
  72. data/public/images/furniture/csv.png +0 -0
  73. data/public/images/furniture/csv_tiny.png +0 -0
  74. data/public/images/furniture/vcard.png +0 -0
  75. data/public/images/furniture/vcard_tiny.png +0 -0
  76. data/public/javascripts/admin/reader.js +22 -1
  77. data/public/stylesheets/sass/admin/reader_group.sass +23 -22
  78. data/public/stylesheets/sass/reader.sass +81 -17
  79. data/radiant-reader-extension.gemspec +3 -1
  80. data/reader_extension.rb +7 -9
  81. data/spec/controllers/accounts_controller_spec.rb +8 -22
  82. data/spec/controllers/admin/messages_controller_spec.rb +0 -12
  83. data/spec/datasets/readers_dataset.rb +41 -38
  84. data/spec/lib/reader_tags_spec.rb +1 -1
  85. data/spec/models/group_spec.rb +89 -22
  86. data/spec/models/message_spec.rb +1 -1
  87. data/spec/models/reader_notifier_spec.rb +1 -1
  88. data/spec/models/reader_page_spec.rb +34 -18
  89. data/spec/models/reader_spec.rb +0 -1
  90. data/spec/spec.opts +4 -3
  91. metadata +51 -28
  92. data/app/views/accounts/_contributions.html.haml +0 -2
  93. data/app/views/accounts/_description.html.haml +0 -2
  94. data/app/views/accounts/_list.html.haml +0 -17
  95. data/app/views/accounts/_profile.html.haml +0 -29
  96. data/app/views/accounts/index.html.haml +0 -23
  97. data/app/views/groups/_all.html.haml +0 -10
  98. data/app/views/site/not_allowed.html.haml +0 -4
  99. data/db/migrate/20100922152338_lock_versions.rb +0 -9
  100. data/db/migrate/20101004074945_unlock_version.rb +0 -9
@@ -42,7 +42,7 @@ class PasswordResetsController < ReaderActionController
42
42
  if @reader.save
43
43
  self.current_reader = @reader
44
44
  flash[:notice] = t('reader_extension.password_updated_notice')
45
- redirect_to dashboard_url
45
+ redirect_to default_welcome_url(@reader)
46
46
  else
47
47
  flash[:error] = t('reader_extension.password_mismatch')
48
48
  render :action => :edit
@@ -5,7 +5,6 @@ class ReaderActionController < ApplicationController
5
5
  helper_method :current_site, :current_site=, :logged_in?, :logged_in_user?, :logged_in_admin?
6
6
 
7
7
  no_login_required
8
- before_filter :set_site_title
9
8
 
10
9
  # reader session is normally required for modifying actions
11
10
  before_filter :require_reader, :except => [:index, :show]
@@ -32,60 +31,25 @@ class ReaderActionController < ApplicationController
32
31
  render
33
32
  end
34
33
 
35
- def default_welcome_url(reader)
36
- if page = reader.find_homepage
37
- page.url
38
- else
39
- reader_url(reader) #TODO make this interesting
40
- end
34
+ def default_welcome_url(reader=nil)
35
+ (reader && reader.home_url) || reader_dashboard_url
41
36
  end
42
37
 
43
38
  protected
44
39
 
45
- # context-setters
46
-
47
- def set_site_title
48
- if defined? Site && current_site
49
- @site_title = current_site.name
50
- @short_site_title = current_site.abbreviation || @site_title
51
- @site_url = current_site.base_domain
52
- else
53
- @site_title = Radiant::Config['site.title']
54
- @short_site_title = Radiant::Config['site.abbreviation'] || @site_title
55
- @site_url = request.host
56
- end
57
- end
58
-
40
+ # NB. ReaderError exceptions are caught in ApplicationController rescue_froms
41
+
59
42
  def require_reader
60
- unless set_reader # set_reader is in ControllerExtension and sets Reader.current while checking for current_reader
43
+ unless set_reader # set_reader is added to ApplicationController and sets Reader.current while checking authentication
61
44
  store_location
62
- respond_to do |format|
63
- format.html {
64
- flash[:explanation] = t('reader_extension.reader_required')
65
- flash[:notice] = t('reader_extension.please_log_in')
66
- redirect_to reader_login_url
67
- }
68
- format.js {
69
- @inline = true
70
- render :partial => 'reader_sessions/login_form'
71
- }
72
- end
45
+ raise ReaderError::LoginRequired, t('reader_extension.please_log_in')
73
46
  false
74
47
  end
75
48
  end
76
49
 
77
50
  def require_activated_reader
78
51
  unless current_reader && current_reader.activated?
79
- respond_to do |format|
80
- format.html {
81
- flash[:explanation] = t('reader_extension.activation_required')
82
- redirect_to reader_activation_url
83
- }
84
- format.js {
85
- @inline = true
86
- render :partial => 'reader_activations/activation_required'
87
- }
88
- end
52
+ raise ReaderError::ActivationRequired, t('reader_extension.activation_required')
89
53
  false
90
54
  end
91
55
  end
@@ -99,12 +63,4 @@ protected
99
63
  end
100
64
  end
101
65
 
102
- def generate_csv(readers=[])
103
- columns = %w{forename surname email phone mobile postal_address}
104
- table = FasterCSV.generate do |csv|
105
- csv << columns.map { |f| t("activerecord.attributes.reader.#{f}") }
106
- readers.each { |r| csv << columns.map{ |f| r.send(f.to_sym) } }
107
- end
108
- end
109
-
110
66
  end
@@ -45,13 +45,15 @@ class ReaderSessionsController < ReaderActionController
45
45
  @reader_session.reader.clear_password = "" # we forget the cleartext version on the first successful login
46
46
  @reader_session.reader.save(false)
47
47
  end
48
- format.html {
49
- flash[:notice] = t('reader_extension.hello').titlecase + " #{@reader_session.reader.name}. " + t('reader_extension.welcome_back')
50
- redirect_back_or_to default_welcome_url(@reader_session.reader)
51
- }
52
- format.js {
53
- redirect_back_with_format(:js)
54
- }
48
+ respond_to do |format|
49
+ format.html {
50
+ flash[:notice] = t('reader_extension.hello').titlecase + " #{@reader_session.reader.name}. " + t('reader_extension.welcome_back')
51
+ redirect_back_or_to default_welcome_url(@reader_session.reader)
52
+ }
53
+ format.js {
54
+ redirect_back_with_format(:js)
55
+ }
56
+ end
55
57
  else
56
58
  respond_to do |format|
57
59
  format.html { render :action => :new }
@@ -72,8 +74,4 @@ class ReaderSessionsController < ReaderActionController
72
74
  redirect_to reader_login_url
73
75
  end
74
76
 
75
- def default_welcome_url(reader=nil)
76
- reader.home_url || dashboard_url
77
- end
78
-
79
77
  end
@@ -1,16 +1,17 @@
1
1
  require 'sanitize'
2
2
  require "sanitize/config/generous"
3
3
  require "fastercsv"
4
+ require "snail_helpers"
4
5
 
5
6
  module ReaderHelper
6
7
  include SnailHelpers
7
8
  include Admin::RegionsHelper
8
9
 
9
10
  def standard_gravatar_for(reader=nil, url=nil)
10
- size = Radiant::Config['forum.gravatar_size'] || 40
11
+ size = Radiant::Config['reader.gravatar_size'] || 40
11
12
  url ||= reader_url(reader)
12
13
  gravatar = gravatar_for(reader, {:size => size}, {:class => 'gravatar offset', :width => size, :height => size})
13
- content_tag(:div, link_to(gravatar, url), :class => "speaker")
14
+ content_tag(:div, link_to(gravatar, url), :class => "speaker", :width => size, :height => size)
14
15
  end
15
16
 
16
17
  def gravatar_for(reader=nil, gravatar_options={}, img_options ={})
@@ -103,7 +104,7 @@ EOM
103
104
  def choose_page(object, field, select_options={})
104
105
  root = Page.respond_to?(:homepage) ? Page.homepage : Page.find_by_parent_id(nil)
105
106
  options = page_option_branch(root)
106
- options.unshift ['<none>', nil]
107
+ options.unshift [t("reader_extension.none_option"), nil]
107
108
  select object, field, options, select_options
108
109
  end
109
110
 
@@ -142,8 +143,27 @@ EOM
142
143
  usps_country_options_for_select(selected, default_selected)
143
144
  end
144
145
 
146
+ def group_options_for_select
147
+ nested_set_options(Group) {|g| "#{'-' * g.level} #{g.name}" }.unshift([t("reader_extension.any_option"), nil])
148
+ end
149
+
150
+ def parent_group_options_for_select(group=nil)
151
+ nested_set_options(Group, group) {|g| "#{'-' * g.level} #{g.name}" }.unshift([t("reader_extension.none_option"), nil])
152
+ end
153
+
145
154
  def email_link(address)
146
155
  mail_to address, nil, :encode => :hex, :replace_at => ' at ', :class => 'mailto'
147
156
  end
148
157
 
158
+ def generate_csv(readers=[])
159
+ columns = %w{forename surname email phone mobile postal_address}
160
+ table = FasterCSV.generate do |csv|
161
+ csv << columns.map { |f| t("activerecord.attributes.reader.#{f}") }
162
+ readers.each { |r| csv << columns.map{ |f| r.send(f.to_sym) } }
163
+ end
164
+ end
165
+
166
+ def generate_vcard(readers=[])
167
+ readers.map(&:vcard).join("\n")
168
+ end
149
169
  end
data/app/models/group.rb CHANGED
@@ -1,15 +1,15 @@
1
- class Group < ActiveRecord::Base
1
+ require 'ancestry'
2
2
 
3
- has_site if respond_to? :has_site
4
- default_scope :order => 'name'
3
+ class Group < ActiveRecord::Base
5
4
 
5
+ has_ancestry
6
+ belongs_to :leader, :class_name => 'Reader'
6
7
  belongs_to :created_by, :class_name => 'User'
7
8
  belongs_to :updated_by, :class_name => 'User'
8
9
  belongs_to :homepage, :class_name => 'Page'
9
10
 
10
11
  has_many :messages
11
12
  has_many :permissions
12
- has_many :pages, :through => :permissions
13
13
  has_many :memberships
14
14
  has_many :readers, :through => :memberships, :uniq => true
15
15
 
@@ -17,11 +17,14 @@ class Group < ActiveRecord::Base
17
17
  validates_presence_of :name, :slug, :allow_blank => false
18
18
  validates_uniqueness_of :name, :slug
19
19
 
20
+ named_scope :any
21
+ named_scope :none, { :conditions => "1 = 0" } # nasty! but doesn't break chains
20
22
  named_scope :with_home_page, { :conditions => "homepage_id IS NOT NULL", :include => :homepage }
21
23
  named_scope :subscribable, { :conditions => "public = 1" }
22
24
  named_scope :unsubscribable, { :conditions => "public = 0" }
23
-
24
- named_scope :from_list, lambda { |ids|
25
+
26
+ named_scope :find_these, lambda { |ids|
27
+ ids = ['NULL'] unless ids && ids.any?
25
28
  { :conditions => ["groups.id IN (#{ids.map{"?"}.join(',')})", *ids] }
26
29
  }
27
30
 
@@ -45,18 +48,41 @@ class Group < ActiveRecord::Base
45
48
  :readonly => false
46
49
  }
47
50
  }
48
-
51
+
49
52
  def self.visible_to(reader=nil)
50
- return all if Radiant.config['readers.public?']
51
- return scoped({:conditions => "1 = 0"}) unless reader # nasty but chainable
52
- return containing(reader) if Radiant.config['readers.confine_to_groups?']
53
- return all
53
+ case Radiant.config['reader.directory_visibility']
54
+ when 'public'
55
+ self.all
56
+ when 'private'
57
+ reader ? self.all : self.none
58
+ when 'grouped'
59
+ (reader && reader.is_grouped?) ? reader.all_visible_groups : self.none
60
+ else
61
+ self.none
62
+ end
54
63
  end
55
64
 
56
65
  def visible_to?(reader=nil)
57
- self.class.visible_to(reader).include? self
66
+ self.class.visible_to(reader).map(&:id).include? self.id
58
67
  end
59
68
 
69
+ def tree
70
+ # can't quite do this in one step, but we can return a scope
71
+ self.root.subtree
72
+ end
73
+
74
+ def tree_ids
75
+ self.root.subtree_ids
76
+ end
77
+
78
+ def members
79
+ Reader.in_groups(subtree)
80
+ end
81
+
82
+ def inherited_permissions
83
+ Permission.to_groups(path)
84
+ end
85
+
60
86
  def url
61
87
  homepage.url if homepage
62
88
  end
@@ -90,12 +116,15 @@ class Group < ActiveRecord::Base
90
116
  # Permission.for_pages named_scope
91
117
  # Group.page_permissions => set of permission objects
92
118
  # Group.pages => set of page objects
93
-
119
+ #
94
120
  def self.define_retrieval_methods(classname)
95
121
  type_scope = "for_#{classname.downcase.pluralize}".intern
96
122
  Permission.send :named_scope, type_scope, :conditions => { :permitted_type => classname }
97
- define_method("#{classname.downcase}_permissions") { self.permissions.send type_scope }
98
- define_method("#{classname.downcase.pluralize}") { self.send("#{classname.to_s.downcase}_permissions".intern).map(&:permitted) }
123
+ define_method("#{classname.downcase}_permissions") { self.inherited_permissions.send type_scope }
124
+ define_method("#{classname.downcase.pluralize}") {
125
+ ids = self.send("#{classname.to_s.downcase}_permissions".intern).map(&:permitted_id)
126
+ classname.constantize.find_these(ids)
127
+ }
99
128
  end
100
129
 
101
130
  private
@@ -103,6 +132,6 @@ private
103
132
  def set_slug
104
133
  self.slug ||= self.name.slugify.to_s
105
134
  end
106
-
135
+
107
136
  end
108
137
 
@@ -1,11 +1,11 @@
1
1
  class Message < ActiveRecord::Base
2
2
 
3
+ has_groups
3
4
  has_site if respond_to? :has_site
4
5
 
5
6
  belongs_to :layout
6
7
  belongs_to :created_by, :class_name => 'User'
7
8
  belongs_to :updated_by, :class_name => 'User'
8
- belongs_to :group
9
9
 
10
10
  has_many :deliveries, :class_name => 'MessageReader', :conditions => ["message_readers.sent_at IS NOT NULL and message_readers.sent_at <= ?", Time.now.to_s(:db)]
11
11
  has_many :recipients, :through => :deliveries, :source => :reader
@@ -21,26 +21,19 @@ class Message < ActiveRecord::Base
21
21
  named_scope :ordinary, { :conditions => "function_id = '' OR function_id IS NULL" }
22
22
  named_scope :published, { :conditions => "status_id >= 100" }
23
23
 
24
- named_scope :belonging_to, lambda {|group|
25
- { :conditions => {:group_id => group }}
26
- }
27
-
28
- named_scope :ungrouped, :conditions => {:group_id => nil}
29
-
30
24
  def filtered_body
31
25
  filter.filter(body)
32
26
  end
33
27
 
34
- # has to return a scope for chainability
35
28
  def possible_readers
36
- group ? group.readers : Reader.scoped({})
29
+ permitted_readers
37
30
  end
38
31
 
39
32
  def undelivered_readers
40
33
  if recipients.any?
41
34
  possible_readers.except(recipients)
42
35
  else
43
- recipients
36
+ possible_readers
44
37
  end
45
38
  end
46
39
 
@@ -57,7 +50,7 @@ class Message < ActiveRecord::Base
57
50
  end
58
51
 
59
52
  def preview(reader=nil)
60
- reader ||= possible_readers.first || Reader.for_user(created_by)
53
+ reader ||= possible_readers.first || Reader.for_user(Reader.current)
61
54
  ReaderNotifier.create_message(reader, self)
62
55
  end
63
56
 
@@ -6,6 +6,10 @@ class Permission < ActiveRecord::Base
6
6
  named_scope :for, lambda { |object|
7
7
  { :conditions => {:permitted_id => object.id, :permitted_type => object.class.name.to_s} }
8
8
  }
9
+
10
+ named_scope :to_groups, lambda { |ids|
11
+ { :conditions => ["permissions.group_id IN (#{ids.map{"?"}.join(',')})", *ids] }
12
+ }
9
13
 
10
14
  end
11
15
 
data/app/models/reader.rb CHANGED
@@ -18,6 +18,8 @@ class Reader < ActiveRecord::Base
18
18
  end
19
19
 
20
20
  belongs_to :user
21
+ before_update :update_user
22
+
21
23
  belongs_to :created_by, :class_name => 'User'
22
24
  belongs_to :updated_by, :class_name => 'User'
23
25
  has_many :message_readers
@@ -26,8 +28,6 @@ class Reader < ActiveRecord::Base
26
28
  has_many :groups, :through => :memberships, :uniq => true
27
29
  accepts_nested_attributes_for :memberships
28
30
 
29
- before_update :update_user
30
-
31
31
  validates_presence_of :name, :email
32
32
  validates_length_of :name, :maximum => 100, :allow_nil => true
33
33
  validates_length_of :password, :minimum => 6, :allow_nil => false, :unless => :existing_reader_keeping_password?
@@ -41,6 +41,7 @@ class Reader < ActiveRecord::Base
41
41
 
42
42
  default_scope :order => 'name ASC'
43
43
  named_scope :any
44
+ named_scope :none, { :conditions => "1 = 0" } # nasty! but doesn't break chains
44
45
  named_scope :active, :conditions => "activated_at IS NOT NULL"
45
46
  named_scope :inactive, :conditions => "activated_at IS NULL"
46
47
  named_scope :imported, :conditions => "old_id IS NOT NULL"
@@ -83,16 +84,65 @@ class Reader < ActiveRecord::Base
83
84
  end
84
85
 
85
86
  def self.visible_to(reader=nil)
86
- return self.all if Radiant.config['readers.public?']
87
- return self.scoped({:conditions => "1 = 0"}) unless reader # nasty but chainable
88
- return self.in_groups(reader.groups) if Radiant.config['readers.confine_to_groups?']
89
- return self.all
87
+ case Radiant.config['reader.directory_visibility']
88
+ when 'public'
89
+ self.all
90
+ when 'private'
91
+ reader ? self.all : self.none
92
+ when 'grouped'
93
+ reader ? self.in_groups(reader.all_visible_group_ids) : self.none
94
+ else
95
+ self.none
96
+ end
90
97
  end
91
98
 
92
99
  def visible_to?(reader=nil)
93
- self.class.visible_to(reader).include? self
100
+ (reader && (reader == self)) || self.class.visible_to(reader).map(&:id).include?(self.id)
101
+ end
102
+
103
+ # returns a useful list of the groups that this person is in and all their ancestor groups.
104
+ # for most authorisation purposes, that's the set of groups of which this reader is considered a member.
105
+ #
106
+ # Returns a scope.
107
+ #
108
+ def all_groups
109
+ Group.find_these(all_group_ids)
110
+ end
111
+
112
+ def all_group_ids
113
+ self.groups.map(&:path_ids).flatten.uniq
114
+ end
115
+
116
+ # Returns a list of the groups that this person is in along with their whole tree of super and subgroups.
117
+ # That's the list of groups that this person can see. It is larger than the list of groups that confer permission:
118
+ # this reader can see subgroups of his own groups in the directory, but he can't see their pages.
119
+ #
120
+ def all_visible_groups
121
+ Group.find_these(all_visible_group_ids)
122
+ end
123
+
124
+ def all_visible_group_ids
125
+ self.groups.map(&:tree_ids).flatten.uniq
126
+ end
127
+
128
+ def can_see? (this)
129
+ permitted_groups = this.permitted_groups
130
+ permitted_groups.empty? or in_any_of_these_groups?(permitted_groups)
131
+ end
132
+
133
+ def in_any_of_these_groups? (grouplist)
134
+ (grouplist & all_groups).any?
94
135
  end
95
136
 
137
+ def has_group? (group)
138
+ all_groups.include?(group)
139
+ end
140
+ alias :is_in? :has_group?
141
+
142
+ def is_grouped?
143
+ groups.any?
144
+ end
145
+
96
146
  # not very i18nal, this
97
147
  def forename
98
148
  read_attribute(:forename) || name.split(/\s+/).first
@@ -102,9 +152,12 @@ class Reader < ActiveRecord::Base
102
152
  read_attribute(:surname) || name.split(/\s+/).last
103
153
  end
104
154
 
155
+ def postal_address?
156
+ !post_line1.blank? && !post_city.blank?
157
+ end
158
+
105
159
  def postal_address
106
160
  Snail.new(
107
- :name => name,
108
161
  :line_1 => post_line1,
109
162
  :line_2 => post_line2,
110
163
  :city => post_city,
@@ -207,24 +260,6 @@ class Reader < ActiveRecord::Base
207
260
  nil
208
261
  end
209
262
  end
210
-
211
- def can_see? (this)
212
- permitted_groups = this.permitted_groups
213
- permitted_groups.empty? or in_any_of_these_groups?(permitted_groups)
214
- end
215
-
216
- def in_any_of_these_groups? (grouplist)
217
- (grouplist & groups).any?
218
- end
219
-
220
- def is_in? (group)
221
- groups.include?(group)
222
- end
223
-
224
- # has_group? is ambiguous: with no argument it means 'is this reader grouped at all?'.
225
- def has_group?(group=nil)
226
- group.nil? ? groups.any? : is_in?(group)
227
- end
228
263
 
229
264
  private
230
265