radiant-reader_group-extension 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,17 +1,21 @@
1
1
  # Reader Group Extension
2
2
 
3
- The basic purpose of this extension is to control access to pages in your public site. It allows you to create a group of readers, and to make pages 9and other site elements) visible only to selected groups. It also provides a useful - and now quite shiny - group-messaging interface and a batch-import function for inviting readers.
3
+ The basic purpose of this extension is to control access to pages in your public site. It allows you to create a group of readers, and to make pages visible only to selected groups. It also provides a useful group-messaging interface using the reader extensions html-mailer and a batch-import function for inviting people into your groups.
4
4
 
5
- In combination with our other extensions it also lets you control access to forums, downloads and assets. I hope other people will find the framework useful and apply it to their own requirements.
5
+ In combination with our other extensions it also lets you control access to forums, downloads and assets. I hope other people will find the framework useful and apply it to their own requirements. Any resource class can be given group-based access control with a single call:
6
6
 
7
- This extension doesn't group your users or affect the admin interface at all, apart from adding (quite a lot of) machinery for looking after groups. As always, readers have their own self-management interface that looks like the rest of your site. They never see the admin pages.
7
+ class Widget << ActiveRecord::Base
8
+ has_groups
9
+ end
8
10
 
9
- This works with multi_site. If you use [our fork](https://github.com/spanner/radiant-paperclipped_multisite-extension/tree) then readers and groups are automatically site-scoped.
11
+ This extension doesn't group your users or affect the admin interface in any way, apart from adding (quite a lot of) machinery for looking after groups and their messages. As always, readers have their own self-management interface that looks like the rest of your site. They never see the admin pages.
12
+
13
+ This works with multi_site (but right now the reader extension doesn't, or not very well). If you use [our fork](https://github.com/spanner/radiant-paperclipped_multisite-extension/tree) then groups are automatically site-scoped.
10
14
 
11
15
  ## Latest
12
16
 
13
- * groups can be marked subscribable, which puts a checkbox to subscribe or unsubscribe on the readers' registration and preference forms. Migration required.
14
- * messages can each have a different layout
17
+ * all group relations are now habtm, so that any object can have one or more groups. Objects with only one group behave as before. Some of the scopes are quite thickety now so there may be bugs in the corners. Migration required.
18
+ * groups can be marked subscribable, which puts a checkbox to subscribe or unsubscribe on the readers' registration and preference forms.
15
19
 
16
20
  ## Status
17
21
 
@@ -19,12 +23,16 @@ This has been brought across from a previous version that grouped users instead
19
23
 
20
24
  ## Requirements
21
25
 
22
- Radiant 0.9.x and the [reader](https://github.com/spanner/radiant-reader-extension/tree) extension.
26
+ Radiant 0.9.x and the [reader](https://github.com/spanner/radiant-reader-extension) extension.
23
27
 
24
28
  ## Installation
25
29
 
26
30
  Once you've got the reader extension in, the rest is easy:
27
31
 
32
+ gem install radiant-reader_group-extension
33
+
34
+ or vendored:
35
+
28
36
  git submodule add git://github.com/spanner/radiant-reader_group-extension.git vendor/extensions/reader_group
29
37
  rake radiant:extensions:reader_group:migrate
30
38
  rake radiant:extensions:reader_group:update
@@ -35,7 +43,7 @@ On github, please.
35
43
 
36
44
  ## Author and copyright
37
45
 
38
- * Copyright spanner ltd 2007-9.
46
+ * Copyright spanner ltd 2007-11.
39
47
  * Released under the same terms as Rails and/or Radiant.
40
48
  * Contact will at spanner.org
41
49
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.2.0
data/app/models/group.rb CHANGED
@@ -19,7 +19,20 @@ class Group < ActiveRecord::Base
19
19
  named_scope :with_home_page, { :conditions => "homepage_id IS NOT NULL", :include => :homepage }
20
20
  named_scope :subscribable, { :conditions => "public = 1" }
21
21
  named_scope :unsubscribable, { :conditions => "public = 0" }
22
-
22
+
23
+ named_scope :attached_to, lambda { |objects|
24
+ conditions = objects.map{|o| "(pp.permitted_type = ? AND pp.permitted_id = ?)" }.join(" OR ")
25
+ binds = objects.map{|o| [o.class.to_s, o.id]}.flatten
26
+ {
27
+ :select => "groups.*, count(pp.group_id) AS pcount",
28
+ :joins => "INNER JOIN permissions as pp on pp.group_id = groups.id",
29
+ :conditions => [conditions, *binds],
30
+ :having => "pcount > 0", # otherwise attached_to([]) returns all groups
31
+ :group => column_names.map { |n| self.table_name + '.' + n }.join(','),
32
+ :readonly => false
33
+ }
34
+ }
35
+
23
36
  def url
24
37
  homepage.url if homepage
25
38
  end
@@ -31,13 +44,32 @@ class Group < ActiveRecord::Base
31
44
  end
32
45
  end
33
46
 
34
- def permission_for(page)
35
- self.permissions.for(page).first
47
+ def admit(reader)
48
+ self.readers << reader
49
+ end
50
+
51
+ def permission_for(object)
52
+ self.permissions.for(object).first
36
53
  end
37
54
 
38
55
  def membership_for(reader)
39
56
  self.memberships.for(reader).first
40
57
  end
58
+
59
+ # we can't has_many through the polymorphic permission relationship, so this is called from has_groups
60
+ # and for eg. Page, it defines:
61
+ # Permission.for_pages named_scope
62
+ # Group.page_permissions => set of permission objects
63
+ # Group.pages => set of page objects
64
+
65
+ def self.define_retrieval_methods(classname)
66
+ type_scope = "for_#{classname.downcase.pluralize}".intern
67
+ Permission.send :named_scope, type_scope, :conditions => { :permitted_type => classname }
68
+ define_method("#{classname.downcase}_permissions") { self.permissions.send type_scope }
69
+ define_method("#{classname.downcase.pluralize}") { self.send("#{classname.to_s.downcase}_permissions".intern).map(&:permitted) }
70
+ end
71
+
72
+
41
73
 
42
74
  end
43
75
 
@@ -1,12 +1,10 @@
1
1
  class Permission < ActiveRecord::Base
2
2
 
3
3
  belongs_to :group
4
- belongs_to :page
4
+ belongs_to :permitted, :polymorphic => true
5
5
 
6
- named_scope :for, lambda { |page|
7
- {
8
- :conditions => ["permissions.page_id = ?", page.id]
9
- }
6
+ named_scope :for, lambda { |object|
7
+ { :conditions => ["permissions.permitted_id = ? and permissions.permitted_type = ?", object.id, object.class.to_s] }
10
8
  }
11
9
 
12
10
  end
@@ -1,7 +1,14 @@
1
1
  - if @message.administrative?
2
2
  %p
3
3
  - if @message.group
4
- = t("group_#{@message.function}_sent_automatically", :name => @message.group.name)
4
+ = t("group_#{@message.function}")
5
+ = t("sent_to_group")
6
+ - elsif @message.groups.any?
7
+ = t("group_#{@message.function}")
8
+ = t("sent_to_these_groups")
9
+ %ul
10
+ - @message.groups.each do |group|
11
+ %li= link_to group.name, admin_group_url(group)
5
12
  - else
6
13
  = t("#{@message.function}_sent_automatically")
7
14
  %ul
@@ -16,4 +23,9 @@
16
23
  - if @message.group
17
24
  %p
18
25
  = t("belongs_to_group", :name => @message.group.name)
19
-
26
+ - elsif @message.groups.any?
27
+ %p
28
+ = t("belongs_to_these_groups")
29
+ %ul
30
+ - @message.groups.each do |group|
31
+ %li= link_to group.name, admin_group_url(group)
@@ -19,6 +19,10 @@ en:
19
19
  private_page_explanation: "The pages selected on the left are only visible to the people selected on the right."
20
20
  group_welcome_sent_automatically: "This message is sent automatically when someone joins the %{name} group."
21
21
  group_invitation_sent_automatically: "This message is sent automatically when someone is invited into the %{name} group."
22
+ sent_to_group: "sent automatically to the %{name} group"
23
+ sent_to_these_groups: "sent automatically to these groups"
24
+ group_welcome: "This is the welcome message"
25
+ group_invitation: "This is the invitation message"
22
26
  all_in_group: "Everyone in the group"
23
27
  inactive_in_group: "Group members who have not activated their account"
24
28
  unsent_in_group: "Group members who have not received this message"
@@ -29,6 +33,7 @@ en:
29
33
  really_delete_group: "Are you sure you want to completely remove the '%{name}' group? This cannot be undone."
30
34
  edit_group: "edit group"
31
35
  belongs_to_group: "This message belongs to the %{name} group."
36
+ belongs_to_these_groups: "This message is sent to these groups:"
32
37
  invite_into_group: "Invite people into the %{name} group"
33
38
  invitation_instructions: "This is a quick way to bring a group of people into the system in one go. Enter a comma-separated list of names and email addresses (one person per line) and after a bit of checking and tweaking on the next page, each of those people will be invited into the system and issued an account with a vaguely adequate username and entirely random password. Anyone who is already here will just be added to the group."
34
39
  check_invitation_message: "Preview invitation message"
@@ -0,0 +1,13 @@
1
+ class MultipleOwnership < ActiveRecord::Migration
2
+ def self.up
3
+ rename_column :permissions, :page_id, :permitted_id
4
+ add_column :permissions, :permitted_type, :string
5
+ Permission.reset_column_information
6
+ Permission.all.each {|p| p.update_attributes(:permitted_type => 'Page') }
7
+ end
8
+
9
+ def self.down
10
+ rename_column :permissions, :permitted_id, :page_id
11
+ remove_column :permissions, :permitted_type
12
+ end
13
+ end
@@ -2,30 +2,30 @@ module GroupedMessage
2
2
 
3
3
  def self.included(base)
4
4
  base.class_eval {
5
- has_group
5
+ has_groups
6
6
 
7
7
  include InstanceMethods
8
- alias_method_chain :possible_readers, :group
9
- alias_method_chain :inactive_readers, :group
8
+ alias_method_chain :possible_readers, :groups
9
+ alias_method_chain :inactive_readers, :groups
10
10
 
11
11
  extend ClassMethods
12
12
  class << self
13
- alias_method_chain :functional, :group
13
+ alias_method_chain :functional, :groups
14
14
  end
15
15
  }
16
16
  end
17
17
 
18
18
  module InstanceMethods
19
- def possible_readers_with_group
20
- group ? group.readers.active : possible_readers_without_group
19
+ def possible_readers_with_groups
20
+ groups.any? ? Reader.in_groups(groups).active : possible_readers_without_groups
21
21
  end
22
- def inactive_readers_with_group
23
- group ? group.readers.inactive : inactive_readers_without_group
22
+ def inactive_readers_with_groups
23
+ groups.any? ? Reader.in_groups(groups).inactive : inactive_readers_without_groups
24
24
  end
25
25
  end
26
26
 
27
27
  module ClassMethods
28
- def functional_with_group(function, group=nil)
28
+ def functional_with_groups(function, group=nil)
29
29
  messages = for_function(function)
30
30
  if group
31
31
  messages.for_group(group).first
data/lib/grouped_model.rb CHANGED
@@ -4,13 +4,13 @@ module GroupedModel
4
4
  end
5
5
 
6
6
  module ClassMethods
7
- def has_group?
7
+ def has_groups?
8
8
  false
9
9
  end
10
+ alias :has_group? :has_groups?
10
11
 
11
- def has_group(options={})
12
- return if has_group?
13
- cattr_accessor :group_recipients, :group_donor
12
+ def has_groups(options={})
13
+ return if has_groups?
14
14
 
15
15
  class_eval {
16
16
  extend GroupedModel::GroupedClassMethods
@@ -21,88 +21,97 @@ module GroupedModel
21
21
  return true
22
22
  end
23
23
  end
24
-
25
- def visible_to_with_groups?(reader)
26
- value_otherwise = visible_to_without_groups?(reader)
27
- return value_otherwise unless group
28
- return false unless reader
29
- return value_otherwise if reader.is_in?(group)
30
- return false
31
- end
32
24
  alias_method_chain :visible_to?, :groups
33
25
  }
34
26
 
35
- belongs_to :group
36
- named_scope :ungrouped, {:conditions => 'group_id IS NULL'}
37
- named_scope :for_group, lambda { |g| {:conditions => ["group_id = ?", g]} }
27
+ has_many :permissions, :as => :permitted
28
+ has_many :groups, :through => :permissions
29
+ accepts_nested_attributes_for :permissions
30
+ Group.define_retrieval_methods(self.to_s)
31
+
32
+ named_scope :ungrouped, {
33
+ :select => "#{self.table_name}.*, count(pp.id) as group_count",
34
+ :joins => "LEFT OUTER JOIN permissions as pp on pp.permitted_id = #{self.table_name}.id AND pp.permitted_type = '#{self.to_s}'",
35
+ :having => "group_count = 0",
36
+ :group => column_names.map { |n| self.table_name + '.' + n }.join(','), # postgres requires that we group by all selected (but not aggregated) columns
37
+ :readonly => false
38
+ }
39
+
40
+ named_scope :grouped, {
41
+ :select => "#{self.table_name}.*, count(pp.id) as group_count",
42
+ :joins => "LEFT OUTER JOIN permissions as pp on pp.permitted_id = #{self.table_name}.id AND pp.permitted_type = '#{self.to_s}'",
43
+ :having => "group_count > 0",
44
+ :group => column_names.map { |n| self.table_name + '.' + n }.join(','),
45
+ :readonly => false
46
+ }
47
+
48
+ named_scope :for_group, lambda { |group|
49
+ {
50
+ :joins => "INNER JOIN permissions as pp on pp.permitted_id = #{self.table_name}.id AND pp.permitted_type = '#{self.to_s}'",
51
+ :conditions => ["pp.group_id = ?)", group.id],
52
+ :group => column_names.map { |n| self.table_name + '.' + n }.join(','),
53
+ :readonly => false
54
+ }
55
+ }
56
+
38
57
  named_scope :visible_to, lambda { |reader|
39
58
  groups = reader.nil? ? [] : reader.groups
40
59
  {:conditions => ["group_id IS NULL OR group_id IN(?)", groups.map(&:id).join(',')]}
41
60
  }
42
-
43
- Group.send(:has_many, self.to_s.pluralize.underscore.intern)
44
-
45
- before_create :get_group
46
- after_save :give_group
47
61
  end
48
- alias :is_grouped :has_group
49
- alias :is_grouped? :has_group?
62
+ alias :has_group :has_groups
50
63
  end
51
64
 
52
65
  module GroupedClassMethods
53
- def has_group?
66
+ def has_groups?
54
67
  true
55
68
  end
56
69
 
57
70
  def visible
58
71
  ungrouped
59
72
  end
60
-
61
- def gets_group_from(association_name)
62
- association = reflect_on_association(association_name)
63
- raise StandardError "can't find group source '#{association_name}" unless association
64
- raise StandardError "#{association.klass} is not grouped and cannot be a group donor" unless association.klass.has_group?
65
- self.group_donor = association_name
66
- end
67
-
68
- def gives_group_to(associations)
69
- associations = [associations] unless associations.is_a?(Array)
70
- # shall we force has_group here?
71
- # shall we assume that gets_group_from follows? and find the association somehow?
72
- self.group_recipients ||= []
73
- self.group_recipients += associations
74
- end
75
73
  end
76
74
 
77
75
  module GroupedInstanceMethods
78
76
 
79
- def visible?
80
- !!group
81
- end
82
-
77
+ # in GroupedPage this is chained to include inherited groups
83
78
  def permitted_groups
84
- [group]
79
+ groups
85
80
  end
86
81
 
87
- protected
82
+ def visible_to_with_groups?(reader)
83
+ return false unless visible_to_without_groups?(reader)
84
+ return true if self.permitted_groups.empty?
85
+ return false if reader.nil?
86
+ return true if reader.is_admin?
87
+ return (reader.groups & self.permitted_groups).any?
88
+ end
88
89
 
89
- def get_group
90
- if self.class.group_donor && group_source = self.send(self.class.group_donor)
91
- self.group = group_source.group
90
+ def group
91
+ if self.permitted_groups.length == 1
92
+ self.permitted_groups.first
93
+ else
94
+ nil
92
95
  end
93
96
  end
97
+
98
+ def visible?
99
+ permitted_groups.empty?
100
+ end
94
101
 
95
- def give_group
96
- if self.class.group_recipients
97
- self.class.group_recipients.each do |association|
98
- send(association).each do |associate|
99
- unless associate.group == group
100
- associate.group = group
101
- associate.save(false)
102
- end
103
- end
104
- end
105
- end
102
+ def permitted_readers
103
+ permitted_groups.any? ? Reader.in_groups(permitted_groups) : Reader.all
104
+ end
105
+
106
+ # GroupedPage also defines a separate has_inherited_group? method
107
+ # so here we don't call permitted_groups
108
+ def has_group?(group)
109
+ return self.groups.include?(group)
106
110
  end
111
+
112
+ def permit(group)
113
+ self.groups << group unless self.has_group?(group)
114
+ end
115
+
107
116
  end
108
117
  end
data/lib/grouped_page.rb CHANGED
@@ -2,50 +2,39 @@ module GroupedPage
2
2
 
3
3
  def self.included(base)
4
4
  base.class_eval {
5
- has_many :permissions
6
- has_many :groups, :through => :permissions
7
- accepts_nested_attributes_for :permissions
8
- has_one :group, :foreign_key => 'homepage_id'
9
-
5
+ has_groups
6
+ has_one :homegroup, :foreign_key => 'homepage_id', :class_name => 'Group'
10
7
  include InstanceMethods
11
-
12
- # any page with a group-marker is never cached
13
- # so that we can continue to return cache hits without care
14
- # this check is regrettably expensive
15
-
16
- def cache?
17
- self.inherited_groups.empty?
18
- end
8
+ alias_method_chain :permitted_groups, :inheritance
19
9
  }
20
10
  end
21
11
 
22
12
  module InstanceMethods
23
13
 
24
- def visible_to?(reader)
25
- permitted_groups = self.inherited_groups
26
- return true if permitted_groups.empty?
27
- return false if reader.nil?
28
- return true if reader.is_admin?
29
- return reader.in_any_of_these_groups?(permitted_groups)
30
- end
31
-
32
14
  # this is all very inefficient recursive stuff
33
- # but to do it in one pass we'd have to build a list of pages anyway
34
- # so there isn't much to gain unless we shift to a different kind of tree
15
+ # but the ancestor pages should be in memory already
16
+ # and the groups check is now a single query
35
17
 
36
18
  def inherited_groups
37
- if (self.parent.nil?)
38
- self.groups
19
+ lineage = self.ancestors
20
+ if lineage.any?
21
+ Group.attached_to(lineage)
39
22
  else
40
- self.groups + self.parent.inherited_groups
23
+ []
41
24
  end
42
25
  end
43
- alias permitted_groups inherited_groups
44
26
 
45
- def has_group?(group)
46
- return self.groups.include?(group)
27
+ def permitted_groups_with_inheritance
28
+ permitted_groups_without_inheritance + inherited_groups
47
29
  end
48
30
 
31
+ # any page with a group-marker is never cached
32
+ # so that we can return cache hits with confidence
33
+ # this call is regrettably expensive
34
+ def cache?
35
+ self.permitted_groups.empty?
36
+ end
37
+
49
38
  def has_inherited_group?(group)
50
39
  return self.inherited_groups.include?(group)
51
40
  end
@@ -9,6 +9,15 @@ module GroupedReader
9
9
  include InstanceMethods
10
10
  alias_method_chain :activate!, :group
11
11
  alias_method_chain :send_functional_message, :group
12
+
13
+ named_scope :in_groups, lambda { |groups|
14
+ {
15
+ :select => "readers.*",
16
+ :joins => "INNER JOIN memberships as mm on mm.reader_id = readers.id",
17
+ :conditions => ["mm.group_id IN (#{groups.map{'?'}.join(',')})", *groups.map{|g| g.is_a?(Group) ? g.id : g}],
18
+ :group => "mm.reader_id"
19
+ }
20
+ }
12
21
  }
13
22
  end
14
23
 
@@ -2,10 +2,8 @@ module SiteControllerExtensions
2
2
 
3
3
  def self.included(base)
4
4
  base.class_eval {
5
- # to control access without ruining the cache we have set Page.cache? = false
6
- # for any page that has a group association. This should prevent the relatively
7
- # few private pages from being cached, and it remains safe to return any cached
8
- # page we find.
5
+ # NB. to control access without disabling the cache we have overridden Page.cache?
6
+ # to return false for any page that has a group association.
9
7
 
10
8
  def find_page_with_group_check(url)
11
9
  page = find_page_without_group_check(url)
@@ -17,11 +15,11 @@ module SiteControllerExtensions
17
15
  show_page_without_group_check
18
16
  rescue ReaderGroup::PermissionDenied
19
17
  if current_reader
20
- flash[:error] = "sorry_access_denied"
18
+ flash[:error] = t("access_denied")
21
19
  redirect_to reader_permission_denied_url
22
20
  else
23
- flash[:explanation] = "page_not_public"
24
- flash[:error] = "please_log_in"
21
+ flash[:explanation] = t("page_not_public")
22
+ flash[:error] = t("please_log_in")
25
23
  store_location
26
24
  redirect_to reader_login_url
27
25
  end
@@ -128,6 +128,8 @@
128
128
  background-image: url(/images/admin/chk_off.png)
129
129
  &.inherited
130
130
  background-image: url(/images/admin/chk_auto.png)
131
+ &.waiting
132
+ background-image: url(/images/admin/spinner.gif)
131
133
 
132
134
  .checked a, .selected a
133
135
  color: #5da454
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{radiant-reader_group-extension}
8
- s.version = "1.1.2"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["spanner"]
12
- s.date = %q{2011-02-11}
12
+ s.date = %q{2011-02-15}
13
13
  s.description = %q{Adds group-based page access control to radiant.}
14
14
  s.email = %q{will@spanner.org}
15
15
  s.extra_rdoc_files = [
@@ -55,6 +55,7 @@ Gem::Specification.new do |s|
55
55
  "db/migrate/001_create_groups.rb",
56
56
  "db/migrate/20090921125654_group_messages.rb",
57
57
  "db/migrate/20091120083119_groups_public.rb",
58
+ "db/migrate/20110214101339_multiple_ownership.rb",
58
59
  "lib/admin_messages_controller_extensions.rb",
59
60
  "lib/group_message_tags.rb",
60
61
  "lib/group_ui.rb",
@@ -36,13 +36,19 @@ class GroupsDataset < Dataset::Base
36
36
  end
37
37
 
38
38
  def add_pages_to_group(g, pp)
39
- g = g.is_a?(Group) ? g : groups(g)
40
- g.pages << pp.map{|p| pages(p)}
39
+ g = groups(g) unless g.is_a? Group
40
+ pp.each {|p|
41
+ p = pages(p) unless p.is_a? Page
42
+ p.permit(g)
43
+ }
41
44
  end
42
45
 
43
46
  def add_readers_to_group(g, rr)
44
- g = g.is_a?(Group) ? g : groups(g)
45
- g.readers << rr.map{|r| readers(r)}
47
+ g = groups(g) unless g.is_a? Group
48
+ rr.each {|r|
49
+ r = readers(r) unless r.is_a? Reader
50
+ g.admit(r)
51
+ }
46
52
  end
47
53
 
48
54
  end
@@ -32,12 +32,14 @@ describe Group do
32
32
 
33
33
  it "should have a group of readers" do
34
34
  group = groups(:normal)
35
+ group.respond_to?(:readers).should be_true
35
36
  group.readers.any?.should be_true
36
37
  group.readers.size.should == 2
37
38
  end
38
39
 
39
40
  it "should have a group of pages" do
40
41
  group = groups(:homed)
42
+ group.respond_to?(:pages).should be_true
41
43
  group.pages.any?.should be_true
42
44
  group.pages.size.should == 2
43
45
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radiant-reader_group-extension
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 1
9
8
  - 2
10
- version: 1.1.2
9
+ - 0
10
+ version: 1.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - spanner
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-11 00:00:00 +00:00
18
+ date: 2011-02-15 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -96,6 +96,7 @@ files:
96
96
  - db/migrate/001_create_groups.rb
97
97
  - db/migrate/20090921125654_group_messages.rb
98
98
  - db/migrate/20091120083119_groups_public.rb
99
+ - db/migrate/20110214101339_multiple_ownership.rb
99
100
  - lib/admin_messages_controller_extensions.rb
100
101
  - lib/group_message_tags.rb
101
102
  - lib/group_ui.rb