radiant-reader_group-extension 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +2 -0
  2. data/README.markdown +44 -0
  3. data/Rakefile +137 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/admin/group_invitations_controller.rb +84 -0
  6. data/app/controllers/admin/groups_controller.rb +4 -0
  7. data/app/controllers/admin/memberships_controller.rb +42 -0
  8. data/app/controllers/admin/permissions_controller.rb +42 -0
  9. data/app/helpers/admin/groups_helper.rb +36 -0
  10. data/app/models/group.rb +43 -0
  11. data/app/models/membership.rb +13 -0
  12. data/app/models/permission.rb +13 -0
  13. data/app/views/admin/group_invitations/new.html.haml +45 -0
  14. data/app/views/admin/group_invitations/preview.html.haml +63 -0
  15. data/app/views/admin/groups/_add_readers.html.haml +0 -0
  16. data/app/views/admin/groups/_form.html.haml +61 -0
  17. data/app/views/admin/groups/_list_head.html.haml +12 -0
  18. data/app/views/admin/groups/_listed.html.haml +25 -0
  19. data/app/views/admin/groups/edit.html.haml +8 -0
  20. data/app/views/admin/groups/index.html.haml +19 -0
  21. data/app/views/admin/groups/new.html.haml +6 -0
  22. data/app/views/admin/groups/remove.html.haml +31 -0
  23. data/app/views/admin/groups/show.html.haml +41 -0
  24. data/app/views/admin/memberships/_reader.html.haml +9 -0
  25. data/app/views/admin/messages/_list_notes.html.haml +9 -0
  26. data/app/views/admin/messages/_message_description.html.haml +7 -0
  27. data/app/views/admin/messages/_message_group.html.haml +3 -0
  28. data/app/views/admin/pages/_listed.html.haml +16 -0
  29. data/app/views/admin/pages/_page_groups.html.haml +17 -0
  30. data/app/views/admin/permissions/_page.html.haml +24 -0
  31. data/app/views/admin/reader_settings/_group_welcomes.html.haml +11 -0
  32. data/app/views/admin/readers/_reader_groups.html.haml +7 -0
  33. data/app/views/messages/show.html.haml +11 -0
  34. data/app/views/reader_activations/_on_activation.html.haml +10 -0
  35. data/app/views/readers/_memberships.html.haml +11 -0
  36. data/app/views/site/not_allowed.html.haml +4 -0
  37. data/config/routes.rb +8 -0
  38. data/db/migrate/001_create_groups.rb +32 -0
  39. data/db/migrate/20090921125654_group_messages.rb +9 -0
  40. data/db/migrate/20091120083119_groups_public.rb +11 -0
  41. data/lib/admin_messages_controller_extensions.rb +15 -0
  42. data/lib/group_message_tags.rb +82 -0
  43. data/lib/group_ui.rb +37 -0
  44. data/lib/grouped_message.rb +38 -0
  45. data/lib/grouped_model.rb +100 -0
  46. data/lib/grouped_page.rb +59 -0
  47. data/lib/grouped_reader.rb +63 -0
  48. data/lib/reader_activations_controller_extensions.rb +21 -0
  49. data/lib/reader_notifier_extensions.rb +14 -0
  50. data/lib/reader_sessions_controller_extensions.rb +21 -0
  51. data/lib/readers_controller_extensions.rb +22 -0
  52. data/lib/site_controller_extensions.rb +37 -0
  53. data/lib/tasks/reader_group_extension_tasks.rake +28 -0
  54. data/pkg/radiant-reader_group-extension-0.9.0.gem +0 -0
  55. data/public/images/admin/chk_auto.png +0 -0
  56. data/public/images/admin/chk_off.png +0 -0
  57. data/public/images/admin/chk_on.png +0 -0
  58. data/public/images/admin/edit.png +0 -0
  59. data/public/images/admin/error.png +0 -0
  60. data/public/images/admin/message.png +0 -0
  61. data/public/images/admin/new-group.png +0 -0
  62. data/public/images/admin/populate.png +0 -0
  63. data/public/images/admin/rdo_off.png +0 -0
  64. data/public/images/admin/rdo_on.png +0 -0
  65. data/public/stylesheets/sass/admin/group.sass +66 -0
  66. data/radiant-reader_group-extension.gemspec +134 -0
  67. data/reader_group_extension.rb +53 -0
  68. data/spec/controllers/readers_controller_spec.rb +44 -0
  69. data/spec/controllers/site_controller_spec.rb +64 -0
  70. data/spec/datasets/group_messages_dataset.rb +32 -0
  71. data/spec/datasets/group_readers_dataset.rb +49 -0
  72. data/spec/datasets/group_sites_dataset.rb +11 -0
  73. data/spec/datasets/groups_dataset.rb +48 -0
  74. data/spec/models/group_spec.rb +45 -0
  75. data/spec/models/message_spec.rb +42 -0
  76. data/spec/models/page_spec.rb +53 -0
  77. data/spec/models/reader_spec.rb +16 -0
  78. data/spec/spec.opts +6 -0
  79. data/spec/spec_helper.rb +36 -0
  80. metadata +184 -0
@@ -0,0 +1,25 @@
1
+ %tr.node.level-1
2
+ - render_region :tbody do |tbody|
3
+ - tbody.name_cell do
4
+ %td.name
5
+ %p
6
+ = link_to group.name, admin_group_url(group)
7
+ %br
8
+ = truncate_words(group.description, 40)
9
+ - tbody.home_cell do
10
+ %td.home
11
+ - if group.homepage
12
+ = link_to group.homepage.title, edit_admin_page_url(group.homepage)
13
+ - else
14
+ none
15
+ - tbody.members_cell do
16
+ %td.members
17
+ = link_to group.readers.count, admin_group_url(group)
18
+ - tbody.pages_cell do
19
+ %td.pages
20
+ = link_to group.pages.count, admin_group_url(group)
21
+ - tbody.modify_cell do
22
+ %td.actions
23
+ = link_to_unless_current image('plus') + ' send invitations', new_admin_group_group_invitation_url(group), :class => 'action'
24
+ = link_to_unless_current image('minus') + ' remove group', admin_group_url(group, :method => 'delete', :confirm => "are you sure you want to completely remove the #{group.name} group?"), :class => 'action'
25
+
@@ -0,0 +1,8 @@
1
+ - include_stylesheet('admin/group')
2
+ - render_region :main do |main|
3
+ - main.edit_header do
4
+ %h1
5
+ Edit group
6
+
7
+ - main.edit_form do
8
+ = render :partial => 'form'
@@ -0,0 +1,19 @@
1
+ - include_stylesheet('admin/group')
2
+
3
+ = render_region :top
4
+
5
+ #groups_table.outset
6
+ %table#groups.index{:cellspacing=>"0", :border=>"0", :cellpadding=>"0"}
7
+ %thead
8
+ = render :partial => 'list_head'
9
+ %tbody
10
+ - @groups.each do |group|
11
+ = render :partial => 'listed', :locals => {:group => group}
12
+
13
+ - render_region :bottom do |bottom|
14
+ - bottom.buttons do
15
+ #actions
16
+ = pagination_for @readers
17
+ %ul
18
+ %li
19
+ = link_to image('plus') + " " + "new group", new_admin_group_url
@@ -0,0 +1,6 @@
1
+ - include_stylesheet('admin/group')
2
+ - render_region :main do |main|
3
+ - main.edit_header do
4
+ %h1 New group
5
+ - main.edit_form do
6
+ = render :partial => "form"
@@ -0,0 +1,31 @@
1
+ - include_stylesheet('admin/group')
2
+ %h1 Remove Group
3
+
4
+ %p
5
+ Are you sure you want to
6
+ %strong.warning
7
+ remove permanently
8
+ the group
9
+ = h @group.name
10
+ ?
11
+
12
+ %p
13
+ The group has
14
+ = @group.readers.count
15
+ = pluralize(@group.readers.count, 'member')
16
+ and
17
+ = @group.pages.count
18
+ = pluralize(@group.pages.count, 'page')
19
+ \. The pages and readers will not be deleted - just dissociated from one another - and removing the group association from those pages may make them visible to everyone.
20
+
21
+ %table#groups.index{:cellspacing=>"0", :border=>"0", :cellpadding=>"0"}
22
+ %thead
23
+ = render :partial => 'list_head'
24
+ %tbody
25
+ = render :partial => 'listed', :locals => {:group => @group}
26
+
27
+ - form_for [:admin, @group], :html => { :method => 'delete' } do
28
+ %p.buttons
29
+ %input.button{:type=>"submit", :value=>"Delete Group"}/
30
+ or
31
+ = link_to 'Cancel', admin_groups_url
@@ -0,0 +1,41 @@
1
+ - include_stylesheet 'admin/group'
2
+ - body_classes << "reversed"
3
+
4
+ %h1
5
+ Group:
6
+ = @group.name
7
+
8
+ = textilize(@group.description)
9
+
10
+
11
+ #group_pages.box.narrow
12
+ %h2
13
+ Private pages
14
+ %ul
15
+ - page = Page.respond_to?(:homepage) ? Page.homepage : Page.find_by_parent_id(nil)
16
+ %div{:id => "page_holder_#{page.id}"}
17
+ = render :partial => 'admin/permissions/page', :object => page
18
+
19
+ #group_people.box.narrow
20
+ %h2
21
+ Group members
22
+ - readers = Reader.find(:all)
23
+ - total = readers.count
24
+ - column_length = (readers.count-1) / 2
25
+ - columns = [readers[0..column_length], readers[column_length+1..readers.count]]
26
+ - columns.each do |column|
27
+ %ul.column
28
+ - column.each do |reader|
29
+ %div{:id => "reader_holder_#{reader.id}"}
30
+ = render :partial => 'admin/memberships/reader', :object => reader
31
+
32
+ #footnotes
33
+ %p
34
+ The pages selected on the left are only visible to the people selected on the right.
35
+
36
+ :javascript
37
+ var h1 = $('group_pages').getHeight();
38
+ var h2 = $('group_people').getHeight();
39
+ var h = (h1 > h2) ? h1 : h2
40
+ $('group_people').setStyle({'height': h + 'px'});
41
+ $('group_pages').setStyle({'height': h + 'px'});
@@ -0,0 +1,9 @@
1
+ - reader ||= @reader
2
+ - group ||= @group
3
+
4
+ - if membership = group.membership_for(reader)
5
+ %li{:class => "fake_checkbox checked", :id => "reader_#{reader.id}"}
6
+ = link_to_remote reader.name, :url => admin_group_membership_url(group, membership), :method => 'delete', :loading => "$('reader_#{reader.id}').addClassName('waiting')", :update => "reader_holder_#{reader.id}"
7
+ - else
8
+ %li{:class => "fake_checkbox unchecked", :id => "reader_#{reader.id}"}
9
+ = link_to_remote reader.name, :url => admin_group_memberships_url(group, :reader_id => reader.id), :loading => "$('reader_#{reader.id}').addClassName('waiting')", :update => "reader_holder_#{reader.id}"
@@ -0,0 +1,9 @@
1
+ - unless message.function.blank?
2
+ %small.function
3
+ = message.function
4
+ - if message.group
5
+ %small.group
6
+ for
7
+ = link_to message.group.name, admin_group_url(message.group)
8
+
9
+
@@ -0,0 +1,7 @@
1
+ - if @message.function
2
+ = @message.function
3
+ message
4
+ - if @message.group
5
+ for the
6
+ = @message.group.name
7
+ group
@@ -0,0 +1,3 @@
1
+ - fields_for @message do |f|
2
+ - if @message.new_record? && @message.group
3
+ = f.hidden_field :group_id
@@ -0,0 +1,16 @@
1
+ %tr.node{:id => "page-#{page.id}", :class =>"level-#{level}"}
2
+ - render_region :page, :locals => {:page => page, :level => level} do |node|
3
+ - node.title_column do
4
+ %td.page{:style => "padding-left: #{(level * 22) + 4}px"}
5
+ %span.w1
6
+ = image('page', :class => "icon", :alt => 'page-icon', :title => '')
7
+ %span.title
8
+ = link_to page.title, edit_admin_page_url(page)
9
+ - node.add_child_column do
10
+ %td.add-child
11
+ = link_to image('add-child', :alt => 'add child'), new_admin_page_child_url(page)
12
+ - node.remove_column do
13
+ %td.remove
14
+ = link_to image('remove', :alt => 'remove page'), remove_admin_page_url(page)
15
+ - page.children.each do |child|
16
+ = render :partial => 'admin/pages/listed', :locals => {:page => child, :level => level+1}
@@ -0,0 +1,17 @@
1
+ - include_stylesheet('admin/group')
2
+ = hidden_field_tag "page[group_ids][]", ""
3
+
4
+ .row
5
+ %p
6
+ Allow access only to:
7
+ - Group.find(:all).each do |g|
8
+ = check_box_tag "page[group_ids][]", g.id, @page.has_inherited_group?(g), {:id => "page_group_#{g.id}", :disabled => @page.group_is_inherited?(g)}
9
+ - if @page.group_is_inherited?(g)
10
+ %label{:for => "page_group_#{g.id}", :class => 'disabled', :title => "group is attached higher in the page tree: can't be detached here"}
11
+ = g.name
12
+ - else
13
+ %label{:for => "page_group_#{g.id}"}
14
+ = g.name
15
+ %br
16
+ %span.formnote
17
+ Leave all groups unchecked for public access.
@@ -0,0 +1,24 @@
1
+ - page ||= @page
2
+ - group ||= @group
3
+
4
+ - liclass = 'loose'
5
+ - liclass = 'attached' if page.has_group?(group)
6
+ - liclass = 'inherited' if page.group_is_inherited?(group)
7
+
8
+ - if permission = group.permission_for(page)
9
+ %li{:class => "fake_checkbox checked", :id => "page_#{page.id}"}
10
+ = link_to_remote page.title, :url => admin_group_permission_url(group, permission), :method => 'delete', :loading => "$('page_#{page.id}').addClassName('waiting')", :update => "page_holder_#{page.id}"
11
+
12
+ - elsif page.has_inherited_group?(group)
13
+ %li{:class => "fake_checkbox inherited", :id => "page_#{page.id}"}
14
+ = page.title
15
+
16
+ - else
17
+ %li{:class => "fake_checkbox unchecked", :id => "page_#{page.id}"}
18
+ = link_to_remote page.title, :url => admin_group_permissions_url(group, :page_id => page.id), :loading => "$('page_#{page.id}').addClassName('waiting')", :update => "page_holder_#{page.id}"
19
+
20
+ - if page.children.any?
21
+ %ul
22
+ - page.children.each do |child|
23
+ %div{:id => "page_holder_#{child.id}"}
24
+ = render :partial => 'admin/permissions/page', :object => child
@@ -0,0 +1,11 @@
1
+ - Group.all.each do |group|
2
+ - [:invitation, :welcome].each do |func|
3
+ - message = Message.functional(:invitation, group)
4
+ %p.ruled
5
+ %label
6
+ = group.name
7
+ = func
8
+ - if message
9
+ = link_to message.subject, edit_admin_message_url(message)
10
+ - else
11
+ = link_to image('plus') + " create message", new_admin_group_message_url(group, :function => func), :class => 'create'
@@ -0,0 +1,7 @@
1
+ %p
2
+ %label{:for=>"reader_groups"} Groups
3
+ - Group.find(:all).each do |group|
4
+ %span.checkbox
5
+ = check_box_tag "reader[group_ids][]", group.id, @reader.is_in?(group), :id => "reader_group_#{group.id}"
6
+ %label{:for => "reader_group_#{group.id}"}
7
+ = group.name
@@ -0,0 +1,11 @@
1
+ .message
2
+ %iframe.message_body{:src => preview_group_message_url(@group, @message)}
3
+
4
+ - content_for :pagetitle do
5
+ = @message.subject
6
+
7
+ - content_for :marginalia do
8
+ margin?
9
+
10
+ - content_for :breadhead do
11
+ = link_to @group.name, @group.homepage.url
@@ -0,0 +1,10 @@
1
+ - if homepage = current_reader.find_homepage
2
+ %p
3
+ Would you like to go straight to the
4
+ = link_to homepage.title, homepage.url
5
+ page?
6
+
7
+ - elsif session[:return_to]
8
+ %p
9
+ Would you like to
10
+ = link_to ("return to the page you were looking at before you started registering", session[:return_to]) + '?'
@@ -0,0 +1,11 @@
1
+ - if Group.subscribable.any?
2
+ %h3
3
+ Subscriptions
4
+ %p{:style => 'margin-top: 0;'}
5
+ - Group.subscribable.each do |group|
6
+ = check_box_tag "reader[group_ids][]", group.id, @reader.is_in?(group), :id => "reader_group_#{group.id}"
7
+ %label{:for => "reader_group_#{group.id}"}
8
+ = group.name
9
+ %span.formnote
10
+ = group.invitation
11
+ %br
@@ -0,0 +1,4 @@
1
+ %h1
2
+ Access Denied
3
+ %p
4
+ Sorry: you don't have permission to view that page.
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.namespace :admin, :path_prefix => 'admin/readers' do |admin|
3
+ admin.resources :groups, :has_many => [:memberships, :permissions, :group_invitations, :messages]
4
+ end
5
+ map.resources :groups, :only => [] do |group|
6
+ group.resources :messages, :only => [:index, :show], :member => [:preview]
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ class CreateGroups < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :groups do |t|
4
+ t.column :name, :string
5
+ t.column :description, :text
6
+ t.column :notes, :text
7
+ t.column :created_at, :datetime
8
+ t.column :updated_at, :datetime
9
+ t.column :created_by_id, :integer
10
+ t.column :updated_by_id, :integer
11
+ t.column :homepage_id, :integer
12
+ t.column :site_id, :integer
13
+ t.column :lock_version, :integer
14
+ end
15
+
16
+ create_table :memberships do |t|
17
+ t.column :group_id, :integer
18
+ t.column :reader_id, :integer
19
+ end
20
+
21
+ create_table :permissions do |t|
22
+ t.column :group_id, :integer
23
+ t.column :page_id, :integer
24
+ end
25
+ end
26
+
27
+ def self.down
28
+ drop_table :groups
29
+ drop_table :memberships
30
+ drop_table :permissions
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ class GroupMessages < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :messages, :group_id, :integer
4
+ end
5
+
6
+ def self.down
7
+ remove_column :messages, :group_id
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class GroupsPublic < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :groups, :public, :boolean
4
+ add_column :groups, :invitation, :text
5
+ end
6
+
7
+ def self.down
8
+ remove_column :groups, :public
9
+ remove_column :groups, :invitation
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module AdminMessagesControllerExtensions
2
+ def self.included(base)
3
+ base.class_eval {
4
+ before_filter :get_group, :only => :new
5
+ }
6
+ end
7
+
8
+ def get_group
9
+ model.group = Group.find(params[:group_id]) if params[:group_id]
10
+ end
11
+
12
+ end
13
+
14
+
15
+
@@ -0,0 +1,82 @@
1
+ module GroupMessageTags
2
+ include Radiant::Taggable
3
+
4
+ class TagError < StandardError; end
5
+
6
+ desc %{
7
+ The root 'group' tag is not meant to be called directly.
8
+ All it does is summon a group object so that its fields can be displayed with eg.
9
+ <pre><code><r:group:name /></code></pre>
10
+
11
+ This tag will not throw an exception if there is no group; it will just disappear.
12
+ }
13
+ tag 'group' do |tag|
14
+ tag.locals.group = @mailer_vars ? @mailer_vars[:@group] : tag.locals.page.group
15
+ tag.expand if tag.locals.group
16
+ end
17
+
18
+ [:name, :description, :url].each do |field|
19
+ desc %{
20
+ Displays the #{field} field of the currently relevant group. Works in email messages too.
21
+
22
+ <pre><code><r:group:#{field} /></code></pre>
23
+ }
24
+ tag "group:#{field}" do |tag|
25
+ tag.locals.group.send(field)
26
+ end
27
+ end
28
+
29
+ desc %{
30
+ Expands if this group has messages.
31
+
32
+ <pre><code><r:group:if_messages>...</r:group:if_messages /></code></pre>
33
+ }
34
+ tag "group:if_messages" do |tag|
35
+ tag.expand if tag.locals.group.messages.ordinary.published.any?
36
+ end
37
+
38
+ desc %{
39
+ Expands if this group does not have messages.
40
+
41
+ <pre><code><r:group:unless_messages>...</r:group:unless_messages /></code></pre>
42
+ }
43
+ tag "group:unless_messages" do |tag|
44
+ tag.expand unless tag.locals.group.messages.ordinary.published.any?
45
+ end
46
+
47
+ desc %{
48
+ Loops through the non-functional messages (ie not welcomes and reminders) that belong to this group
49
+ and that have been sent, though not necessarily to the present reader (which is the point, really).
50
+
51
+ <pre><code><r:group:messages:each>...</r:group:messages:each /></code></pre>
52
+ }
53
+ tag "group:messages" do |tag|
54
+ tag.locals.messages = tag.locals.group.messages.ordinary.published
55
+ tag.expand
56
+ end
57
+ tag "group:messages:each" do |tag|
58
+ result = []
59
+ tag.locals.messages.each do |message|
60
+ tag.locals.message = message
61
+ result << tag.expand
62
+ end
63
+ result
64
+ end
65
+
66
+ # overridden to add group scope:
67
+
68
+ desc %{
69
+ Returns the url of the show-message page
70
+
71
+ <pre><code><r:message:url /></code></pre>
72
+ }
73
+ tag "message:url" do |tag|
74
+ if tag.locals.group
75
+ group_message_path(tag.locals.group, tag.locals.message)
76
+ else
77
+ message_path(tag.locals.message)
78
+ end
79
+ end
80
+
81
+
82
+ end
data/lib/group_ui.rb ADDED
@@ -0,0 +1,37 @@
1
+ module GroupUI
2
+
3
+ def self.included(base)
4
+ base.class_eval do
5
+
6
+ attr_accessor :group
7
+ alias_method :groups, :group
8
+
9
+ def load_default_regions_with_group
10
+ load_default_regions_without_group
11
+ @group = load_default_group_regions
12
+ end
13
+
14
+ alias_method_chain :load_default_regions, :group
15
+
16
+ protected
17
+
18
+ def load_default_group_regions
19
+ returning OpenStruct.new do |group|
20
+ group.edit = Radiant::AdminUI::RegionSet.new do |edit|
21
+ edit.main.concat %w{edit_header edit_form}
22
+ edit.form.concat %w{edit_group edit_timestamp edit_buttons edit_membership edit_pages}
23
+ end
24
+ group.index = Radiant::AdminUI::RegionSet.new do |index|
25
+ index.thead.concat %w{name_header home_header members_header pages_header modify_header}
26
+ index.tbody.concat %w{name_cell home_cell members_cell pages_cell modify_cell}
27
+ index.bottom.concat %w{buttons}
28
+ end
29
+ group.remove = group.index
30
+ group.new = group.edit
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,38 @@
1
+ module GroupedMessage
2
+
3
+ def self.included(base)
4
+ base.class_eval {
5
+ is_grouped
6
+
7
+ include InstanceMethods
8
+ alias_method_chain :possible_readers, :group
9
+ alias_method_chain :inactive_readers, :group
10
+
11
+ extend ClassMethods
12
+ class << self
13
+ alias_method_chain :functional, :group
14
+ end
15
+ }
16
+ end
17
+
18
+ module InstanceMethods
19
+ def possible_readers_with_group
20
+ group ? group.readers.active : possible_readers_without_group
21
+ end
22
+ def inactive_readers_with_group
23
+ group ? group.readers.inactive : inactive_readers_without_group
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def functional_with_group(function, group=nil)
29
+ messages = for_function(function)
30
+ if group
31
+ messages.for_group(group).first
32
+ else
33
+ messages.ungrouped.first
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,100 @@
1
+ module GroupedModel
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ def is_grouped?
8
+ false
9
+ end
10
+
11
+ def is_grouped(options={})
12
+ return if is_grouped?
13
+ cattr_accessor :group_recipients, :group_donor
14
+
15
+ class_eval {
16
+ extend GroupedModel::GroupedClassMethods
17
+ include GroupedModel::GroupedInstanceMethods
18
+
19
+ def visible_to?(reader)
20
+ return true unless group
21
+ return false unless reader
22
+ return true if reader.is_user?
23
+ return true if reader.is_in?(group)
24
+ return false
25
+ end
26
+
27
+ }
28
+
29
+ belongs_to :group
30
+ named_scope :ungrouped, {:conditions => 'group_id IS NULL'}
31
+ named_scope :for_group, lambda { |g| {:conditions => ["group_id = ?", g]} }
32
+ named_scope :visible_to, lambda { |reader|
33
+ groups = reader.nil? ? [] : reader.groups
34
+ {:conditions => ["group_id IS NULL OR group_id IN(?)", groups.map(&:id).join(',')]}
35
+ }
36
+
37
+ Group.send(:has_many, self.to_s.pluralize.underscore.intern)
38
+
39
+ before_create :get_group
40
+ after_save :give_group
41
+ end
42
+ end
43
+
44
+ module GroupedClassMethods
45
+ def visible
46
+ ungrouped
47
+ end
48
+
49
+ def is_grouped?
50
+ true
51
+ end
52
+
53
+ def gets_group_from(association_name)
54
+ association = reflect_on_association(association_name)
55
+ raise StandardError "can't find group source '#{association_name}" unless association
56
+ raise StandardError "#{association.klass} is not grouped and cannot be a group donor" unless association.klass.is_grouped?
57
+ self.group_donor = association_name
58
+ end
59
+
60
+ def gives_group_to(associations)
61
+ associations = [associations] unless associations.is_a?(Array)
62
+ # shall we force is_grouped here?
63
+ # shall we assume that gets_group_from follows? and find the association somehow?
64
+ self.group_recipients ||= []
65
+ self.group_recipients += associations
66
+ end
67
+ end
68
+
69
+ module GroupedInstanceMethods
70
+
71
+ def visible?
72
+ !!group
73
+ end
74
+
75
+ def permitted_groups
76
+ [group]
77
+ end
78
+
79
+ protected
80
+
81
+ def get_group
82
+ if self.class.group_donor && group_source = self.send(self.class.group_donor)
83
+ self.group = group_source.group
84
+ end
85
+ end
86
+
87
+ def give_group
88
+ if self.class.group_recipients
89
+ self.class.group_recipients.each do |association|
90
+ send(association).each do |associate|
91
+ unless associate.group == group
92
+ associate.group = group
93
+ associate.save(false)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end