radiant-reader_group-extension 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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