Coder Social home page Coder Social logo

groupify's Introduction

Groupify

Build Status Coverage Status Code Climate Inline docs

Adds group and membership functionality to Rails models. Defines a polymorphic relationship between a Group model and any member model. Don't need a Group model? Use named groups instead to add members to named groups such as :admin or "Team Rocketpants".

Compatibility

The following ORMs are supported:

  • ActiveRecord 4.x, 5.x
  • Mongoid 4.x, 5.x, 6.x

The following Rubies are supported:

  • MRI Ruby 2.2, 2.3, 2.4
  • JRuby 9000

The following databases are supported:

  • MySQL
  • PostgreSQL
  • SQLite
  • MongoDB

Installation

Add this line to your application's Gemfile:

gem 'groupify'

And then execute:

$ bundle

Or install it yourself as:

$ gem install groupify

Setup

Active Record

Execute:

$ rails generate groupify:active_record:install

This will generate an initializer, Group model, GroupMembership model, and migrations.

Modify the models and migrations as needed, then run the migration:

$ rake db:migrate

Set up your member models:

class User < ActiveRecord::Base
  groupify :group_member
  groupify :named_group_member
end

class Assignment < ActiveRecord::Base
  groupify :group_member
end

Mongoid

Execute:

$ rails generate groupify:mongoid:install

Set up your member models:

class User
  include Mongoid::Document
  
  groupify :group_member
  groupify :named_group_member
end

Advanced Configuration

Groupify Model Names

The default model names for groups and group memberships are configurable. Add the following configuration in config/initializers/groupify.rb to change the model names for all classes:

Groupify.configure do |config|
  config.group_class_name = 'MyCustomGroup'
  # ActiveRecord only
  config.group_membership_class_name = 'MyCustomGroupMembership'
end

The group name can also be set on a model-by-model basis for each group member by passing the group_class_name option:

class Member < ActiveRecord::Base
  groupify :group_member, group_class_name: 'MyOtherCustomGroup'
end

Note that each member model can only belong to a single type of group (or child classes of that group).

Member Associations on Group

Your group class can be configured to create associations for each expected member type. For example, let's say that your group class will have users and assignments as members. The following configuration adds users and assignments associations on the group model:

class Group < ActiveRecord::Base
  groupify :group, members: [:users, :assignments], default_members: :users
end

The default_members option sets the model type when accessing the members association. In the example above, group.members would return the users who are members of this group.

If you are using single table inheritance, child classes inherit the member associations of the parent. If your child class needs to add more members, use the has_members method.

Example:

class Organization < Group
  has_members :offices, :equipment
end

Mongoid works the same way by creating Mongoid relations.

Usage

Create groups and add members

group = Group.new
user = User.new

user.groups << group
# or
group.add user

user.in_group?(group)
# => true

# Add multiple members at once
group.add(user, widget, task)

Remove from groups

users.groups.destroy(group)          # Destroys this user's group membership for this group
group.users.delete(user)             # Deletes this group's group membership for this user

Named groups

user.named_groups << :admin
user.in_named_group?(:admin)        # => true
user.named_groups.destroy(:admin)

Check if two members share any of the same groups:

user1.shares_any_group?(user2)          # Returns true if user1 and user2 are in any of the same groups
user2.shares_any_named_group?(user1)    # Also works for named groups

Query for groups & members:

User.in_group(group)                # Find all users in this group
User.in_named_group(:admin)         # Find all users in this named group
Group.with_member(user)             # Find all groups with this user

User.shares_any_group(user)         # Find all users that share any groups with this user
User.shares_any_named_group(user)   # Find all users that share any named groups with this user

Check if member belongs to any/all groups

User.in_any_group(group1, group2)               # Find users that belong to any of these groups
User.in_all_groups(group1, group2)              # Find users that belong to all of these groups
Widget.in_only_groups(group2, group3)           # Find widgets that belong to only these groups

widget.in_any_named_group?(:foo, :bar)          # Check if widget belongs to any of these named groups
user.in_all_named_groups?(:manager, :poster)    # Check if user belongs to all of these named groups
user.in_only_named_groups?(:employee, :worker)  # Check if user belongs to only these named groups

Merge one group into another:

# Moves the members of source into destination, and destroys source
destination_group.merge!(source_group)

Membership Types

Membership types allow a member to belong to a group in a more specific way. For example, you can add a user to a group with membership type of "manager" to specify that this user has the "manager role" on that group.

This can be used to implement role-based authorization combined with group authorization, which could be used to mass-assign roles to groups of resources.

It could also be used to add users and resources to the same "sub-group" or "project" within a larger group (say, an organization).

# Add user to group as a specific membership type
group.add(user, as: 'manager')

# Works with named groups too
user.named_groups.add 'Company', as: 'manager'

# Query for the groups that a user belongs to with a certain role
user.groups.as(:manager)
user.named_groups.as('manager')
Group.with_member(user).as('manager')

# Remove a member's membership type from a group
group.users.delete(user, as: 'manager')         # Deletes this group's 'manager' group membership for this user
user.groups.destroy(group, as: 'employee')      # Destroys this user's 'employee' group membership for this group
user.groups.destroy(group)                      # Destroys any membership types this user had in this group

# Find all members that have a certain membership type in a group
User.in_group(group).as(:manager)

# Find all members of a certain membership type regardless of group
User.as(:manager)    # Find users that are managers, we don't care what group

# Check if a member belongs to any/all groups with a certain membership type
user.in_all_groups?(group1, group2, as: 'manager')

# Find all members that share the same group with the same membership type
Widget.shares_any_group(user).as("Moon Launch Project")

# Check is one member belongs to the same group as another member with a certain membership type
user.shares_any_group?(widget, as: 'employee')

Note that adding a member to a group with a specific membership type will automatically add them to that group without a specific membership type. This way you can still query groups and find the member in that group. If you then remove that specific membership type, they still remain in the group without a specific membership type.

Removing a member from a group will bulk remove any specific membership types as well.

group.add(manager, as: 'manager')
manager.groups.include?(group)              # => true

manager.groups.delete(group, as: 'manager')
manager.groups.include?(group)              # => true

group.add(employee, as: 'employee')
employee.groups.delete(group)
employee.in_group?(group)                   # => false
employee.in_group?(group, as: 'employee')   # => false

Using for Authorization

Groupify was originally created to help implement user authorization, although it can be used generically for much more than that. Here are some examples of how to do it.

With CanCan

class Ability
  include CanCan::Ability

  def initialize(user)
    # Implements group-based authorization
    # Users can only manage assignment which belong to the same group.
    can [:manage], Assignment, Assignment.shares_any_group(user) do |assignment|
      assignment.shares_any_group?(user)
    end
  end
end

With Authority

# Whatever class represents a logged-in user in your app
class User
  groupify :named_group_member
  include Authority::UserAbilities
end

class Widget
  groupify :named_group_member
  include Authority::Abilities
end

class WidgetAuthorizer  < ApplicationAuthorizer
  # Implements group-based authorization using named groups.
  # Users can only see widgets which belong to the same named group.
  def readable_by?(user)
    user.shares_any_named_group?(resource)
  end

  # Implements combined role-based and group-based authorization.
  # Widgets can only be updated by users that are employees of the same named group.
  def updateable_by?(user)
    user.shares_any_named_group?(resource, as: :employee)
  end

  # Widgets can only be deleted by users that are managers of the same named group.
  def deletable_by?(user)
    user.shares_any_named_group?(resource, as: :manager)
  end
end

user = User.create!
user.named_groups.add(:team1, as: :employee)

widget = Widget.create!
widget.named_groups << :team1

widget.readable_by?(user) # => true
user.can_update?(widget)  # => true
user.can_delete?(widget)  # => false

With Pundit

class PostPolicy < Struct.new(:user, :post)
  # User can only update a published post if they are admin of the same group.
  def update?
    user.shares_any_group?(post, as: :admin) || !post.published?
  end

  class Scope < Struct.new(:user, :scope)
    def resolve
      if user.admin?
        # An admin can see all the posts in the group(s) they are admin for
        scope.shares_any_group(user).as(:admin)
      else
        # Normal users can only see published posts in the same group(s).
        scope.shares_any_group(user).where(published: true)
      end
    end
  end
end

Backwards-Incompatible Releases

0.9+ - Dropped support for Rails 3.2 and Ruby 1.9 - 2.1

Groupify 0.9 added support for Rails 5.1, and dropped support for EOL'ed versions of Ruby, Rails, ActiveRecord, and Mongoid.

ActiveRecord 5.1 no longer supports passing arguments to collection associations. Because of this, the undocumented syntax groups.as(:membership_type) is no longer supported.

0.8+ - Name Change for group_memberships Associations (ActiveRecord only)

Groupify 0.8 changed the ActiveRecord adapter to support configuring the same model as both a group and a group member. To accomplish this, the internal group_memberships association was renamed to be different for groups and members. If you were using it, please be aware that you will need to change your code. This association is considered to be an internal implementation details and not part of the public API, so please don't rely on it if you can avoid it.

0.7+ - Polymorphic Groups (ActiveRecord only)

Groupify < 0.7 required a single Group model used for all group memberships. Groupify 0.7+ supports using multiple models as groups by implementing polymorphic associations. Upgrading requires adding a new group_type column to the group_memberships table and populating that column with the class name of the group. Create the migration by executing:

$ rails generate groupify:active_record:upgrade

And then run the migration:

$ rake db:migrate

Please note that this migration may block writes in MySQL if your group_memberships table is large.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Contributors

See a list of contributors here.

groupify's People

Contributors

byronduenas avatar dwbutler avatar fourfour avatar juhazi avatar reed avatar rposborne avatar wadestuart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

groupify's Issues

New Group (Unsaved) has members somehow

ActiveRecord 4.0.3
Ruby 2.1.1

(rdb:1) Group.new.members
CACHE (0.0ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "group_memberships" ON "group_memberships"."member_id" = "users"."id" AND "group_memberships"."member_type" = 'User' WHERE "group_memberships"."member_type" = 'User'

<ActiveRecord::Relation [#<User id: 11, email: "[email protected]", encrypted_password: "$2a$10$FVCvMYpaA94WS3lDeQFQJukLAZMn0gX6ufWwxWTst/g1...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 2, current_sign_in_at: "2014-03-20 17:48:21", last_sign_in_at: "2014-03-19 22:56:37", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2014-03-19 22:56:20", updated_at: "2014-03-20 17:48:21", first_name: "Austin", last_name: "Fonacier">, #<User id: 1, email: "[email protected]", encrypted_password: "$2a$10$qTu5iuYFvUL/aOVZnFwsBe0nv1VSGivwB5Teugwweuti...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2014-03-19 22:56:19", updated_at: "2014-03-19 22:56:19", first_name: "Riley", last_name: "Schulist">]>

Adding users to groups

@dwbutler I am a newbie on rails 5 and cannot seem to add users to my groups. my group_membership table is empty.
Can someone help me with an example? even the syntax would help.

find named group by member

I want to create many named groups with many different members, is there a way to find named group by member?
Group.with_member() returns empty association.

Cannot get single table inheritance work

Thank you for your work, it a great gem. But I have some issue while using single table inheritance. Models are listed below:

class User < ActiveRecord::Base
  groupify :group_member, group_class_name: 'Department'
end

class Group < ActiveRecord::Base
  groupify :group
end

class GroupMembership < ActiveRecord::Base
  groupify :group_membership
end

class Department < Group
  has_ancestry
  has_members :users
  
  def add_employee(u)
    self.add u, as: :employee
  end
  
  def add_manager(u)
    self.add u, as: :manager
  end
  
  def remove_employee(u)
    u.groups.delete self
  end
  
  def remove_manager(u)
    u.groups.delete self
  end
  
end

I can add a user to a department as well, but when I trying to delete it from the department, it not works, and the sql is:

DELETE FROM "group_memberships" WHERE "group_memberships"."member_id" = ? AND "group_memberships"."member_type" = ? AND "group_memberships"."group_type" = 'Group' AND "group_memberships"."group_id" = 1 AND "group_memberships"."group_type" = ?  [["member_id", 1], ["member_type", "User"], ["group_type", "Group"]]

But the relationship generated in group_memberships, the group_type is Department, so nothing happened after the deletion sql.

This also happens while retrieving department members, department.users runs this sql:

SELECT DISTINCT "users".* FROM "users" INNER JOIN "group_memberships" ON "users"."id" = "group_memberships"."member_id" WHERE "group_memberships"."group_id" = ? AND "group_memberships"."group_type" = ? AND "group_memberships"."member_type" = 'User' AND "group_memberships"."membership_type" = ?  [["group_id", 1], ["group_type", "Group"], ["membership_type", :manager]]

It wont find anything because of wrong group_type, can you give more detail of using single table inheritance? It would be a big help, thank you

Removing Users From a Group [QUESTION]

I have been playing around with groupify before using in a project of mine and I am having issue removing users from a given group whenever I do the following line:

group.users.delete(user, as: 'manager')  

I am able to correctly create the group, then add a user with the correct membership type. However, when I try and delete a user using the line above, I get the following error:

NoMethodError: undefined method `users' for #<Group id: 1, type: nil>

This also occurs anytime I try and use "group.users". I am able to croeectly delete a user using:

user.groups.destroy(group, as: 'manager') 

Any help would be appreciated.

My models are as follows:

class GroupMembership < ActiveRecord::Base
  groupify :group_membership
end

class Group < ActiveRecord::Base
  groupify :group
end

class User < ApplicationRecord
  groupify :group_member
  groupify :named_group_member
  
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :invitable
end

Error on add user a team on Rails 5 Beta

private method `clear_association_cache' called for #<User:0x007fc2638bf150>
Did you mean? clear_transaction_record_state

clear_association_cache is private in Rails5. … but I don`t fix it =/

Groupify support unique groups scoped to user?

Can I use groupify to create user owned groups that are only accessible to the owners? For example, a Facebook user can create a user group and assign friends to that group, but others cannot see this group. Is this easily done with Groupify?

Please explain - an example would be fantastic, time permitting.

Getting started

Hello,
i'm new in rails and i want to create an app, that can create groups, i read the README but i dont know how to star, what code put on the controller for example, could someone help me please.

Creating a named group without adding user

Hi @dwbutler ,

How can I create a named group without adding a user to it?

class GroupController < ApplicationController
def creategroup
Group.create!(params[:group])
end
def createnamedgroup
??
end
end

Thanks for your assistance.

ActiveRecord Statement Invalid for Group queries

On Postgresql.. running the .in_all_groups(Group1, Group2) or .in_only_groups(Group1, Group2) produces the following error:

PG::InvalidColumnReference: ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...G COUNT(group_memberships.group_id) = 2  ORDER BY users.last...

Appears to be something about doing a group_by in Postgres without specifying the same select fields?

Extend to deal with group managers/leaders?

This is working great so far, how would I extend or use this to deal with managers or leaders of a group?

example - I am using named groups, if I add say 5 users to a group called :team1 how would I set one (or more) as managers of that particular group?

I'm not sure if the gem can handle this currently

Change the name of the group_membership

I think that the group_membership is not a good name for the join table. It should be renamed to membership.

I can do a pull request if you agree to merge it :)

NameError: uninitialized constant User::GroupMembership

I followed the documentation, manually creating the migration file and the models. I already had a User model in my app:

user.rb:
class User < ActiveRecord::Base
authenticates_with_sorcery!

validates :name, presence: true
validates :password, length: { minimum: 4 }
validates :password, confirmation: true
validates :password_confirmation, presence: true

validates :email, uniqueness: true

has_many :testimonials
has_many :materials

groupify :group_member
groupify :named_group_member

end

class Assignment < ActiveRecord::Base
groupify :group_member
end

group.rb:
class Group < ActiveRecord::Base
groupify :group, members: [:users, :assignments], default_members: :users
end

GroupMembership.rb:
class GroupMembership < ActiveRecord::Base
groupify :group_membership
end

schema.rb:
ActiveRecord::Schema.define(version: 20150301133633) do

create_table "group_memberships", force: :cascade do |t|
t.string "member_type"
t.integer "member_id"
t.integer "group_id"
t.string "group_name"
t.string "membership_type"
end

add_index "group_memberships", ["group_id"], name: "index_group_memberships_on_group_id"
add_index "group_memberships", ["group_name"], name: "index_group_memberships_on_group_name"
add_index "group_memberships", ["member_id", "member_type"], name: "index_group_memberships_on_member_id_and_member_type"

create_table "groups", force: :cascade do |t|
t.string "type"
end

create_table "materials", force: :cascade do |t|
t.string "level"
t.text "description"
t.string "link"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "title"
t.integer "user_id"
end

add_index "materials", ["user_id"], name: "index_materials_on_user_id"

create_table "testimonials", force: :cascade do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

add_index "testimonials", ["user_id", "created_at"], name: "index_testimonials_on_user_id_and_created_at"
add_index "testimonials", ["user_id"], name: "index_testimonials_on_user_id"

create_table "tests", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "users", force: :cascade do |t|
t.string "email", null: false
t.string "crypted_password"
t.string "salt"
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
end

add_index "users", ["email"], name: "index_users_on_email", unique: true

end

I run my console in sandbox mode and do:
group = Group.new
user = User.new

group.add user

And my console spews out NameError: uninitialized constant User::GroupMembership

What's going on?

How to use Groupify for my app ?

I am trying to add group functionality for my users. I have never used the Groupify gem before .

I would like to start with Groups such as : Admin, Moderator, Artist, User. The members of these groups are all users but have certain privileges . And I would also like to give members of these basic groups the ability to create new groups for mostly discussion purposes and information sharing. I am not even sure How to create the form for the group and how how to create the group, with its attributes, and membership.

I tried using Groupify on the console:

user=User.new

group=Group.new

group.add user

user.in_group?(group)

=> false

It returns false instead of true. I am stuck and though I read the documentation, I do not seem to understand the implementation:

Here is my just created group controller :

class GroupsController < ApplicationController
def new
@group = Group.new
end
def create
if @group.save
  flash.now[:success]= "Group #{@group.group_name} successfully created"
  redirect_to @group
else
  render 'new'
end
end
def show
@group = Group.find(params[:id])
end
def edit
@group = Group.find(params[:id])
end
def update
@group = Group.find(params[:id])
if @group.update_attributes(group_params)
  flash.now[:success] = "Group updated !"
  redirect_to @group
 else
  render 'edit'
  end
 end

def destroy
end
private 
def group_params
  params.require(:type, :group_name)
end
end

My group model:

class Group < ActiveRecord::Base
groupify :group, members: [:users, :assignments], default_members: :users
end

My membership model :

class GroupMembership < ActiveRecord::Base
groupify :group_membership
end
Some relevant part of my user model:

class User < ActiveRecord::Base
attr_accessor :remember_member
groupify :group_member
groupify :named_group_member

before_save { self.email = email.downcase }
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy

has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy

has_many :passive_relationships, class_name:  "Relationship",
                               foreign_key: "followed_id",
                               dependent:   :destroy
end

And finally my group migration:

 class CreateGroups < ActiveRecord::Migration
 def change
 create_table :groups do |t|
  t.string   :type      # Only needed if using single table inheritance
  t.text     :description
 end

create_table :group_memberships do |t|
  t.string     :member_type     # Necessary to make polymorphic members work
  t.integer    :member_id       # The id of the member that belongs to this group
  t.integer    :group_id        # The group to which the member belongs
  t.string     :group_name      # The named group to which a member belongs (if using)
  t.string     :membership_type # The type of membership the member belongs with
 end

add_index :group_memberships, [:member_id, :member_type]
add_index :group_memberships, :group_id
add_index :group_memberships, :group_name
end
end

Thanks for your help. Also do you have an example application/tutorial where groupify is implemented >?
PS: I am not an advanced Rails developer

Count functions

Is there a way to know the number of member for a group ?

how to include groupify in a rails engine

Hi,

I am trying to use groupify in a rails engine. Now I have "require 'groupify' " in my engine.rb, however I keep getting the errors saying "undefined method `groupify'" from my active_record models. Do you have any suggestions for correctly including groupify in an engine?

Sample Application Broken

I tried integrating your sample application, but when i try to create a new group i get a crash here:

NoMethodError at /groups/new
undefined method `name' for #<Group id: nil, type: nil>

   </div>
  <% end %>
  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Any ideas?

Either enable custom association options or set inverse_of for the relations

The associations generated by groupify and has_members methods lack the inverse_of option.

It should be possible to enable this on a per-call basis to keep backwards compatibility.

Example usage:

class Organization < Group
  has_members :managers,
    inverse_of: true
end

Which would generate

class Organization < ActiveRecord::Base
  has_many :managers,
    through: :group_memberships_as_group,
    inverse_of: :organizations,
    ... # The rest of the options 
end

Alternatively allow the developer to merge in a (whitelisted?) set of extra keys to the association options hash.

Example of that:

class Organization < Group
  has_members :managers,
    inverse_of: :organizations
end

For more info on inverse_of specifically: https://www.viget.com/articles/exploring-the-inverse-of-option-on-rails-model-associations

NoMethodError: undefined method `groups' for {:as=>"manager"}:Hash

I am having an issue with the type feature in the gem.

I actually spun up a test app and followed the setup in the spec. Maybe I am missing something in the setup.

https://gist.github.com/fourfour/0cf77b1ad8344f40106d

Error:
1.9.3-p545 :007 > g.add(u, as: 'manager')
(0.1ms) begin transaction
SQL (0.9ms) INSERT INTO "group_memberships" ("group_id", "member_id", "member_type") VALUES (?, ?, ?) ["group_id", 1], ["member_id", 1], ["member_type", "User"] commit transaction
NoMethodError: undefined method `groups' for {:as=>"manager"}:Hash

Thanks a ton!

changed group_membership but it still looking for a table group_membership

When i do
User.as(:admin)
knowing that I changed the group_membership to organization_membership in the initializer
I got
User Load (1.0ms) SELECT users.* FROM users INNER JOIN organization_memberships ON organization_memberships.member_id = users.id AND organization_memberships.member_type = 'User' WHERE group_memberships.membership_type = 'admin' LIMIT 11
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'group_memberships.membership_type' in 'where clause': SELECT users.* FROM users INNER JOIN organization_memberships ON organization_memberships.member_id = users.id AND organization_memberships.member_type = 'User' WHERE group_memberships.membership_type = 'admin' LIMIT 11
Why it's still looking for group_memberships table ?

Example of model associations...

Hi, I really appreciate your work and I'm confident I can get it working with a little help.

My app has 3 member classes: users, storybooks, and stories. I have the following line in my Group model:

groupify :group, members: [:users, :storybooks, :stories], default_members: :users

Am I to assume that the Groupify gem DOES NOT take care of certain parameters within an association? For example, I am able to add users to groups, but when I delete a group, I want to also delete the GroupMembership records associated with that group, such as with dependent: :destroy. How can I accomplish this?

Do I also need to specify that a user has_many group_memberships, etc, etc, etc or are these inherent in the Groupify gem?

Thanks in advance.

Named Groups to be a Group

Hi, I love this gem and what it provides, but one thing I don't understand is why the named groups do not have a "Group" class. Since the rest of the gem flows this way, wouldn't it make sense to do that?

I'm only just starting to work with this gem, so I don't have a pull request ready to go since I'm not sure how to implement that.

Let me know your thoughts.

Inverse of has_members missing as a feature?

Taking the active_record_spec example setup with only the relevant parts:

class User < ActiveRecord::Base
  groupify :group_member
end

class Manager < User
end

class Group < ActiveRecord::Base
  groupify :group
end

class Organization < Group
  has_members :managers
end

With this, Organization class has a has_many :managers association.

How do I get an inverse_of association of that as has_many :organizations relation in the Manager class?

I would expect there to be a has_groups method available for group members.

Double Membership

Hey David.

One thing I noticed when integrating this in my app is that Users are added twice to each group if you pass in a type. I am not sure if this is the intended outcome.

It will create a GroupMembership for type User and `Manager'. I think it would be better to only create one record for each user. I guess a User can have multiple types, but maybe serializing type or something. Just a thought.

https://gist.github.com/fourfour/1200f2f312fbf4294661

User.in_named_group("some_string).count causes crash

this code:

def create

    other_users = User.in_named_group( team_params[:team_name] )

    if other_users.count == 0 #<-- crash here
      redirect_to request.referrer, :flash => { :error => "Group Already Exists!" }
    end

end

Causes the following crash:

PG::UndefinedFunction at /teams
ERROR:  operator does not exist: integer = uuid
LINE 1: ...p_memberships" ON "group_memberships"."member_id" = "users"....
                                                             ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.

Any ideas?

Restricting the names of named groups

I love this gem btw. Thanks for making it! I'm up and running and was wondering:

  • How can I restrict/control the name of a named group?

I'd thought about

class GroupMembership < Groupify::ActiveRecord::NamedGroupCollection
  validates_format_of :group_name, with: /\AWingedLoraxOfThe(North|South|East|West)/Z/
end

To make sure my Lorax's are only the winged variety of the cardinal directions. Would this be the best-practice or is there perhaps something inside the docs I've missed for doing the same thing?

Group default_members

Just curious what does the "default_members" parameter does when you set it in the model? I want to just make sure before including this.

Examples?

Would love to use this gem, but are there any examples available to have a look?

Cheers

users won't stick in groups

I set it up as per the documentation (I think).

But then in the console:

user=User.new group=Group.new group.add user user.in_group?(group)

=> false

Returns false, instead of true.

Problem.

I don't get it. I've a few different things and no joy. I'm stuck.

My latest attempt is here: https://github.com/Yorkshireman/sebcoles/tree/setup3 (setup3 branch)

I have a previous attempt on setup2.

There is little difference between the two branches (the migrations and subsequent scheme.rb were SLIGHTLY different).

Help!

Rails 5.1.5 Support

Rails 5.1.5 appears to be causing failures throughout this gem's test suite

ActiveRecord::HasManyThroughOrderError:
       Cannot have a has_many :through association 'User#groups' which goes through 'User#group_memberships_as_member' before the through association is defined.

This appears to be caused by this recent commit in rails.

rails/rails@652258e

This may be a bug in rails, but wanted to flag this here for others.

Passing a block of extensions to groupify generated associations

Given the models in active_record_spec I would like to:

class User < ActiveRecord::Base
  groupify :group_member do
    def some_operation_on_the_association
      proxy_association.owner.something
    end        
  end
end

to end up as if I had defined the association with the block:

class User < ActiveRecord::Base
  has_many :groups, { ... } do
    def some_operation_on_the_association
      proxy_association.owner.something
    end        
  end
end

This should not interfere with the pre-existing extend: GroupAssociationExtensions style options defined in the associations.

Does this feature seem feasible? I did not create a commit yet due to multiple directly related open branches.

Getting uninitialized constant Assignment

So i just found Groupify last night and i think this is what i need for my app. I have followed the documentation and created the migrations and the models that are suggested. But when i try to create a new group from console i get this error:

from bin/rails:4:in `<main>'irb(main):010:0> Group.create
NameError: uninitialized constant Assignment

My user.rb:

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  acts_as_group_member
  acts_as_named_group_member

  class Assignment < ActiveRecord::Base
    acts_as_group_member
  end
end

My group.rb

class Group < ActiveRecord::Base  
  acts_as_group :members => [:users, :assignments], :default_members => :users
end

My group_membership.rb

class GroupMembership < ActiveRecord::Base  
  acts_as_group_membership
end

My migration file:

class CreateGroups < ActiveRecord::Migration
 def change
    create_table :groups do |t|
      t.string     :type      # Only needed if using single table inheritence
    end

    create_table :group_memberships do |t|
      t.string     :member_type   # Needed to make polymorphic members work
      t.integer    :member_id   # The member that belongs to this group
      t.integer    :group_id    # The group to which the member belongs
      t.string     :group_name    # Links a member to a named group (if using named groups)
    end

    add_index :group_memberships, [:member_id, :member_type]
    add_index :group_memberships, :group_id
    add_index :group_memberships, :group_name
  end
end

Rails 5: deprecation warning regarding the use of uniq

ActiveSupport::DeprecationException:
DEPRECATION WARNING: uniq is deprecated and will be removed from Rails 5.1 (use distinct instead) (called from […])
# /var/lib/gems/2.3.0/gems/groupify-0.8.0/lib/groupify/adapter/active_record/group_member.rb:25:in `block (2 levels) in <module:GroupMember>'
[…]

This (group_member.rb:25) is the only place the warning was triggered in my case, but a cursory search over the groupify source code indicates uniq is used in other places as well.

Question re Group Names

What's the reasoning behind storing the group_name in every membership record,
rather than in the group record itself? Can groups be renamed after creation?
How is it ensured that all members use the same group_name? e.g. after renaming of the group.

can't complete migration

When running the migration running into this error:

[aberrios@localhost ghetti]$ rake db:migrate                        
  ActiveRecord::SchemaMigration Load (0.5ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Migrating to Group (20150320143918)
   (0.2ms)  BEGIN
== 20150320143918 Group: migrating ============================================
-- create_table(:groups)
   (11.3ms)  CREATE TABLE "groups" ("id" serial primary key, "type" character varying(255)) 
   -> 0.0129s
-- create_table(:group_memberships)
   (1.9ms)          SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
          FROM pg_class t, pg_constraint f
         WHERE f.conrelid = t.oid
           AND f.contype = 'f'
           AND t.relname = 'group_memberships'
           AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )

   (1.1ms)          SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
          FROM pg_class t, pg_constraint f
         WHERE f.conrelid = t.oid
           AND f.contype = 'f'
           AND t.relname = 'group_memberships'
           AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )

   (21.3ms)  CREATE TABLE "group_memberships" ("id" serial primary key, "member_type" character varying(255), "member_id" integer, "group_id" integer, "group_name" character varying(255), "membership_type" character varying(255), CONSTRAINT fk_group_memberships_member_id FOREIGN KEY ("member_id") REFERENCES "members" ("id"), CONSTRAINT fk_group_memberships_group_id FOREIGN KEY ("group_id") REFERENCES "groups" ("id")) 
PG::UndefinedTable: ERROR:  relation "members" does not exist

Migration

[aberrios@localhost ghetti]$ cat db/migrate/20150320143918_group.rb 
class Group < ActiveRecord::Migration
  def change
    create_table :groups do |t|
      t.string     :type      # Only needed if using single table inheritance
    end

    create_table :group_memberships do |t|
      t.string     :member_type     # Necessary to make polymorphic members work
      t.integer    :member_id       # The id of the member that belongs to this group
      t.integer    :group_id        # The group to which the member belongs
      t.string     :group_name      # The named group to which a member belongs (if using)
      t.string     :membership_type # The type of membership the member belongs with
    end

    add_index :group_memberships, [:member_id, :member_type]
    add_index :group_memberships, :group_id
    add_index :group_memberships, :group_name
  end
end

If I change member_id to user_id then the migration completes but then when trying to add a user to a group in a controller I get a complaint that group_memberships.member_id field does not exist

`in_only_groups` doesn't match on group IDs

It appears that in_only_groups finds members that are members of exactly the same number of groups passed to the method. However, it does not make sure to match on the exact same groups.

For instance, if a user is in group1, group2, and group3, and I find User.in_only_groups(group2, group3, group4), it will return my user, even though they are not in the exact same 3 groups.

Am I misunderstanding this method?

https://github.com/dwbutler/groupify/blob/master/lib/groupify/adapter/active_record/group_member.rb#L133

All my tests fail after following groupify tutorial.

ActiveRecord::StatementInvalid: ActiveRecord::StatementInvalid: SQLite3::SQLException: table groups has no column named name: INSERT INTO "groups" ("name", "id") VALUES ('MyString', 980190962)

Is the error I am getting and it present in every test. I am not sure what I did as I am a relatively new rails dev. I tried doing my own group functionality and I think groupify migrated some of the tables I had already created? Do you have any idea of what I am talking about? It is probably my fault and I will roll back to an earlier commit before I started trying to add my own group functionality and then follow the groupify docs to re-instrument.

https://travis-ci.org/fergyfresh/carpool
https://github.com/fergyfresh/carpool/tree/groupify

Save using member resource id

Hey.

I was wondering if there is a way to save group members by there id's (collection_singular_ids)? For example I'd like to have a multi select of members in my group form.

f.input :user_ids, as: :select, collection: User.all, input_html: { multiple: true }

Group.find(1).user_ids = [1, 2, 3]

But I am finding the group_type is not being set (default STI behaviour) so no results as returned when queried as it expects group_type to be set to Group.

If I use

 Group.find(1).add(User.find(1)) 

The group_type is set on the group_membership.

Cheers,
Tom

Groupify

Hi
I am novice on rails and i am working on a grouping-social project .
the problem is that adding a user in a group with a certain membership-type, automatically adds 2 records, one is the default one and two is the specific type we want.
my grouping system has join request and pending requests that i want to use with groupify's membership type, I dont need records with nil membershiptype !!
Can you provide the gem without duplicate records ?
Sincerely Yours :D
am , valeh

Filtering group members based on membership_type

Hey - great work on this gem!

I have created a group where each member has a membership_type. When a given user looks up their groups Id like to show them the other users in that group but I want to restrict it based on the current user's membership_type.

For example, a group 'owner' can see all members where as an 'editor' can only see other editors.

Is there a standard approach to something like that?

I've mucked around and come up with this but I'm not sure if its the best approach:

class ProjectGroup < Group
  def visible_members(user)
    user_membership = group_memberships.where(
      member_id: user.id,
      group_type: type
    ).first
    members.as(user_membership.membership_type)
  end
end

Thanks!

Setting a group as a group member breaks has_members associations on that group

The use case I tried to implement is:

  • have Teams, Users, and Tags as AR models
  • Teams and Tags inherit from Groups, which are groupify groups
  • Users are groupify :group_member
  • Users are members of Teams and Tags
  • Teams are also members of Tags

issue can be demonstrated by making the following change to active_record_spec.rb

class Group < ActiveRecord::Base
  groupify :group, members: [:users, :widgets, "namespaced/members"], default_members: :users
end

class Organization < Group
  groupify :group_member #TODO: breaks has_many :managers 

  has_members :managers
end

results are following failures:

  1) Groupify::ActiveRecord when using groups finds all members of group
     Failure/Error: expect(organization.managers).to include(manager)

       expected #<ActiveRecord::Associations::CollectionProxy []> to include #<Manager id: 1, name: nil, type: "Manager">
       Diff:
       @@ -1,2 +1,2 @@
       -[#<Manager id: 1, name: nil, type: "Manager">]
       +[]

     # ./spec/active_record_spec.rb:234:in `block (3 levels) in <top (required)>'

  2) Groupify::ActiveRecord when using groups when merging groups merges incompatible groups as long as all the source members can be moved to the destination
     Failure/Error: expect(destination.users.to_a).to include(user)

       expected [] to include #<User id: 1, name: nil, type: nil>
       Diff:
       @@ -1,2 +1,2 @@
       -[#<User id: 1, name: nil, type: nil>]
       +[]

     # ./spec/active_record_spec.rb:378:in `block (4 levels) in <top (required)>'

  3) Groupify::ActiveRecord when using groups when merging groups moves the members from source to destination and destroys the source
     Failure/Error: expect(destination.managers).to include(manager)

       expected #<ActiveRecord::Associations::CollectionProxy []> to include #<Manager id: 2, name: nil, type: "Manager">
       Diff:
       @@ -1,2 +1,2 @@
       -[#<Manager id: 2, name: nil, type: "Manager">]
       +[]

     # ./spec/active_record_spec.rb:341:in `block (4 levels) in <top (required)>'

The bug seems to be in this generated SQL query:

SELECT DISTINCT "users".*
FROM "users"
INNER JOIN "group_memberships"
ON "users"."id" = "group_memberships"."member_id"
WHERE "group_memberships"."member_id" = ?
AND "group_memberships"."member_type" = ?
AND "group_memberships"."member_type" = 'User'
[["member_id", 2], ["member_type", "Group"]]

As is apparent, it will never return any matching records.

Cannot properly use groupify when using prefixed table names

Hi,

Suppose I have this Groupify configuration

Groupify.configure do |config|
  # Configure the default group class name.
  # Defaults to 'Group'
  config.group_class_name = 'Company::Group'

  # Configure the default group membership class name.
  # Defaults to 'GroupMembership'
  config.group_membership_class_name = 'Company::GroupMembership'
end

And I have my groupify tables prefixed accordingly :

class GroupifyMigration < ActiveRecord::Migration
  def change
    create_table :company_groups do |t|
      t.string     :type
    end

    create_table :company_group_memberships do |t|
      t.references :member, polymorphic: true, index: true, null: false
      t.references :group, polymorphic: true, index: true

      # The named group to which a member belongs (if using)
      t.string     :group_name, index: true

      # The membership type the member belongs with
      t.string     :membership_type

      t.timestamps
    end
  end
end

If I try to do the following command :

Company::User.shares_any_group Company::User.last

It result in the following query :

Company::User Load (0.2ms)  SELECT  "company_users".* FROM "company_users" ORDER BY "company_users"."id" DESC LIMIT ?  [["LIMIT", 1]]
DEPRECATION WARNING: uniq is deprecated and will be removed from Rails 5.1 (use distinct instead) (called from irb_binding at (irb):20)
  Company::Group Load (0.2ms)  SELECT DISTINCT "company_groups".* FROM "company_groups" INNER JOIN "company_group_memberships" ON "company_groups"."id" = "company_group_memberships"."group_id" WHERE "company_group_memberships"."member_id" = ? AND "company_group_memberships"."member_type" = ? AND "company_group_memberships"."group_type" = ?  [["member_id", 5], ["member_type", "Company::User"], ["group_type", "Company::Group"]]
DEPRECATION WARNING: uniq is deprecated and will be removed from Rails 5.1 (use distinct instead) (called from irb_binding at (irb):20)
  Company::User Load (0.5ms)  SELECT DISTINCT "company_users".* FROM "company_users" INNER JOIN "company_group_memberships" ON "company_group_memberships"."member_id" = "company_users"."id" AND "company_group_memberships"."member_type" = ? WHERE "group_memberships"."group_id" = 2  [["member_type", "Company::User"]]

Which raises in a ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: group_memberships.group_id: error because in the request, it references the group_memberships table and not the company_group_memberships table.

Also if I try this :

Company::User.last.named_groups << :admin

I get an ActiveRecord::RecordInvalid: Validation failed: Group must exist error. There again, it would seem Groupify does not use the Company::Group model

Here is a test repository to reproduce this behavior : https://github.com/Tuxii/namespace_groupify

So in other words, Groupify allows to rename its models via the config initializer, but that does not mean you can rename the table as well right ?

Relationship between two models

Hi,
Is it possible to create join between two models, I am not sure if its right but the idea is more like this:
User1 with Manager role will add a POST1
User2 with Coworker role can send a request to join POST1 to be a Team member of POST1.
User1 can send request to let user2 be part of Teams
User1/User2 can approve or reject if they got request.
If anyone removes the relation so it's removed from both.

Any idea or way to do that.

Named Groups? (similar to GitHub organizations data model)

Hi there!

Happy to close this issue if groupify doesn't handle but i figured this was pretty darn perfect.

My use-case:

User should be able to create a 2 teams named "rails" and "javascript".
User should be able to be members of both as :admin or as a :collaborator

I see group name on the membership model but not sure how to prevents putting incorrect names on memberships.

failed to add a named group

After doing
user = User.first
user.named_groups << :admin

I got
(0.3ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Group must exist

Do I have to create the group first ? how?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.