Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Groupify v1.0.0: Better STI/polymorphism and various fixes #61

Open
wants to merge 206 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
206 commits
Select commit Hold shift + click to select a range
00c923c
Add the ability to specify a map of association names to class names
joelvh May 9, 2017
87fadf5
Introduce `has_member` with `:class_name` option for custom class on …
joelvh May 9, 2017
c7d3e64
Make it easier to add subclassed group associations with STI
joelvh May 10, 2017
46e6ad8
Updated docs for `has_group`
joelvh May 10, 2017
7696974
Override `<<` on collections to properly add membership_type
joelvh May 10, 2017
7dcaa1a
Add member to members collection rather than the group to groups coll…
joelvh May 10, 2017
f177d85
Don't set default, for backwards-compatibility
joelvh May 10, 2017
4bcff73
Query based group class (to support subclasses)
joelvh May 10, 2017
337f3c9
Remove unused option
joelvh May 10, 2017
1710727
Clear association cache after deleting associated models
joelvh May 10, 2017
3df2ea4
Fix query to be extensible
joelvh May 10, 2017
2a84b72
Refactored to mimic `<<` method returning `false` if an invalid argum…
joelvh May 10, 2017
c9b8d58
Add to collection with options (e.g. membership type)
joelvh May 10, 2017
ae7bec1
Specify group class name when merging in case it's a subclass
joelvh May 10, 2017
0166f5d
Remove `group_type` based on group class because we handle STI separa…
joelvh May 10, 2017
b87d1f1
Use ActiveRecord `base_class` in case member is STI
joelvh May 10, 2017
74b3044
Revert back to using association and assume STI
joelvh May 10, 2017
abd27e4
Added ability for `group.add` to throw a validation exception
joelvh May 10, 2017
76d45cc
Remove unneeded `default_members_association_name` feature
joelvh May 10, 2017
5694c15
Don't use `validate!` to ensure Rails 4.0.x compatibility
joelvh May 18, 2017
c18a2f4
Move extensions modules to separate files
joelvh May 18, 2017
ef466f2
Consolidate queries for deleting records
joelvh May 18, 2017
de70b89
Refactor to handle groups and members associations with same logic
joelvh May 18, 2017
f4ef818
Moved `delete` and `destroy` methods to shared module
joelvh May 18, 2017
16a5896
Move finder methods to appropriate modules
joelvh May 18, 2017
fcaf155
Reuse delete/destroy logic
joelvh May 18, 2017
34d659b
Require modules
joelvh May 18, 2017
3eb9a59
Mark methods protected
joelvh May 18, 2017
a5b73cf
Define `add` explicitly as raising an exception instead of using `opts`
joelvh May 18, 2017
4ec1ae1
Alias `add` and `<<` in shared module
joelvh May 18, 2017
8f64968
Simplify abstractions and fix some reference errors
joelvh May 18, 2017
3fd5074
Fixed alias methods
joelvh May 19, 2017
19f76bb
Fix `super` reference
joelvh May 19, 2017
c52d063
Added `group_type` to query to properly build membership record
joelvh May 19, 2017
539b97c
Added tests to make sure inverse associations are updated after `dele…
joelvh May 19, 2017
aa82ec4
Added tests to make sure members and groups aren't added more than once
joelvh May 19, 2017
31bcafe
Added tests to check subclassing
joelvh May 19, 2017
a3935e7
Fix whitespace
joelvh Jul 1, 2017
eea48e0
Use `merge` instead of `where`
joelvh Jul 4, 2017
adf691d
Refactor `where` with `merge`
joelvh Jul 4, 2017
3dfd19e
Let Rails generate the queries on `group_id` and `group_type`
joelvh Jul 4, 2017
341eae1
Update `group_type` for completeness
joelvh Jul 4, 2017
2e87738
Remove commented code
joelvh Jul 4, 2017
52517a3
Remove duplicated extension modules from rebase
joelvh Jul 5, 2017
972b80d
Simplify member criteria
joelvh Jul 5, 2017
f213396
helpers to query on polymorphic groups and members
joelvh Jul 5, 2017
1734420
Use merge and helpers for query
joelvh Jul 5, 2017
68e9028
clearer logic
joelvh Jul 5, 2017
04c6cf3
Add ability to call `has_group :custom_groups, "CustomGroup"`
joelvh Jul 8, 2017
7d321d3
Clarify logic and simplify some steps
joelvh Jul 8, 2017
31f2afe
Pass each record as separate parameter
joelvh Jul 8, 2017
c6bd4cd
Include type column when counting matches
joelvh Jul 8, 2017
0a05de7
Removed unused method
joelvh Jul 8, 2017
c5db985
Added helper method to make it easier to read SQL criteria
joelvh Jul 8, 2017
77c438e
DRY logic
joelvh Jul 8, 2017
efe9df9
Simplify logic when finding matching named group
joelvh Jul 8, 2017
b990b0f
Simplify variable use
joelvh Jul 8, 2017
d5af10b
Simplify membership query building
joelvh Jul 8, 2017
98fb5a9
Code spacing
joelvh Jul 8, 2017
3440327
Refer to `arel_table` consistently
joelvh Jul 9, 2017
6c5c226
Removed unused methods
joelvh Aug 1, 2017
3b0f9a3
Formatted for clarity
joelvh Aug 1, 2017
922c102
Consolidate association extensions and move helpers to `ActiveRecord`…
joelvh Aug 2, 2017
9ed5007
Fix polymorphic adding of members to groups by bypassing association …
joelvh Aug 2, 2017
1f82fda
Added `memberships_merge` helper to named groups
joelvh Aug 2, 2017
f9d3216
Add helper method to infer class and association names
joelvh Aug 3, 2017
00fe96b
Clean up `membership_type` usage and check presence better/minimally
joelvh Aug 3, 2017
19f73cb
Improve inferring parent model when model can be group and member
joelvh Aug 3, 2017
8295cbd
Consolidate `membership_merge` logic
joelvh Aug 3, 2017
f32e082
Exclude `ActiveRecord::AssociationTypeMismatch` test because it is no…
joelvh Aug 3, 2017
64fecca
Build query via chain instead of control logic
joelvh Aug 3, 2017
3dbe428
Set blank values to nil
joelvh Aug 3, 2017
4192d31
Throw "type mismatch" exception when adding to association
joelvh Aug 3, 2017
eea79b3
Update Mongoid with some similar code cleanup as ActiveRecord
joelvh Aug 3, 2017
856377f
Fixed filter fallback in tests
joelvh Aug 3, 2017
f708dba
Fix flattening arrays
joelvh Aug 3, 2017
a47b625
DRY up Mongoid similar to ActiveRecord
joelvh Aug 3, 2017
e2cf3e5
Fix Mongoid test
joelvh Aug 3, 2017
3614e2a
Rails 4.0 doesn't like merging empty hash, so we build the query step…
joelvh Aug 4, 2017
c7f09bd
Fix test to use specific IDs
joelvh Aug 4, 2017
a3ba035
Clear association cache on child after creating membership
joelvh Aug 4, 2017
33f7af2
Fix tests for Postgres which changes the order of records
joelvh Aug 4, 2017
028f88c
Fix tests to compare arrays properly
joelvh Aug 4, 2017
df9bdb8
Simplify methods by removing outdated aliasing
joelvh Aug 4, 2017
228aa6c
Add test to make sure associations are reset after deletion
joelvh Aug 4, 2017
f47a514
Add default`members` association in more intuitive spot
joelvh Aug 4, 2017
e3240ab
Fix named group support in Rails 5
dwbutler Aug 4, 2017
cfbbc8f
Clean up `has_member` and `has_group` and allow association options
joelvh Aug 4, 2017
49f592d
Fix wrong variable name reference
joelvh Aug 4, 2017
8271701
Add `has_groups` and `has_group` to Mongoid
joelvh Aug 4, 2017
ed43370
Add proper default class
joelvh Aug 4, 2017
3f051c8
Only look up base class if needed
joelvh Aug 4, 2017
5781d41
Fix Rails 4.0 - 4.1, which don't support the `required` option
dwbutler Aug 4, 2017
7dc1405
Added `Groupify.ignore_base_class_inference_errors` to swallow infere…
joelvh Aug 5, 2017
50bdc00
Use `source_type` instead of `class_name`
joelvh Aug 5, 2017
3f69372
Consolidate `has_member` and `has_group` and add error handling
joelvh Aug 5, 2017
31ed051
Use base class
joelvh Aug 5, 2017
e4b0fdd
Update tests with `autoload` to help resolve circular class dependencies
joelvh Aug 5, 2017
92f08fb
Moved test classes to separate files for autoloading
joelvh Aug 5, 2017
38bdff0
Indicate value of variable better in exception (e.g. when nil)
joelvh Aug 5, 2017
fd36319
Simplify
joelvh Aug 5, 2017
691a28d
Add option to return a string version of inferred class name rather t…
joelvh Aug 5, 2017
a18bd85
Fix variable name
joelvh Aug 6, 2017
435e294
Add `class_name` in case the default group class is a STI subclass
joelvh Aug 6, 2017
2cf9875
Use default group class when base class can't be resolved
joelvh Aug 6, 2017
e056730
Make default `members` and `groups` association names configurable
joelvh Aug 6, 2017
ae3ba76
Abstract out extension methods
joelvh Aug 6, 2017
cfd9b98
Introduce `PolymorphicChildren` collection class to
joelvh Aug 6, 2017
895b345
Disabled autoloading in tests (for now)
joelvh Aug 6, 2017
39bd88b
Remove unused argument
joelvh Aug 6, 2017
3e3acb0
Split out polymorphic classes for multiple uses
joelvh Aug 6, 2017
bae0e02
Properly merge queries
joelvh Aug 6, 2017
788ef79
Allow modifying query
joelvh Aug 6, 2017
d1ed8f6
Crate a relation
joelvh Aug 6, 2017
b6485b6
Scope properly
joelvh Aug 6, 2017
74c6b57
Fix variable name
joelvh Aug 6, 2017
900ce84
Fix query nesting
joelvh Aug 6, 2017
6196619
Fix type
joelvh Aug 6, 2017
a43048f
Add `pretty_print`
joelvh Aug 6, 2017
f76d1b3
Rename block argument for consistency
joelvh Aug 6, 2017
a895ea4
Use `instance_eval` for consistency
joelvh Aug 6, 2017
92f513b
Fix up some consistency in the code
joelvh Aug 6, 2017
9fd75bd
Remove duplicated class definitions
joelvh Aug 6, 2017
b59feaf
Fix postgres by changing from `GROUP BY` to `DISTINCT ON`
joelvh Aug 6, 2017
a8a22f7
Added test to make sure polymorphic groups are unique
joelvh Aug 6, 2017
7ca1813
Make sure that `count` is correct based on PostgreSQL DISTINCT vs. GR…
joelvh Aug 6, 2017
d1686b1
Make sure memberships are added properly based on role
joelvh Aug 6, 2017
c592f93
Moved parent/child logic into `ParentProxy` class to consolidate and …
joelvh Aug 7, 2017
09f1964
Fix for group deletion deleting group membership after merge... not s…
joelvh Aug 7, 2017
09cadcb
Simplify class
joelvh Aug 7, 2017
083509e
Move query logic to `ParentQueryBuilder` class
joelvh Aug 7, 2017
e0b7037
Clarify logic
joelvh Aug 7, 2017
57b46ea
DRY up `has_many` creation
joelvh Aug 7, 2017
3dd6891
Rename "query" to "scope"
joelvh Aug 7, 2017
679da0e
determine child type
joelvh Aug 7, 2017
96a588f
Fix Rails 4.0-5.0 tests using `extending` on model class when chainin…
joelvh Aug 10, 2017
0d57f0a
Fix DISTINCT queries in PostgreSQL for SELECT vs COUNT
joelvh Aug 10, 2017
039a94a
Drop support for Rails 4.0
joelvh Aug 10, 2017
1100d09
Use helper to build query
joelvh Aug 10, 2017
b0dbaa6
DRY up code with helpers
joelvh Aug 10, 2017
ba6015d
Make default member class configurable
joelvh Aug 10, 2017
f3b76b8
Fix `merge` to use `all` scope for Rails 4.0-5.0
joelvh Aug 10, 2017
f036617
Make public method names more user friendly
joelvh Aug 10, 2017
52c528d
Fix `children_association` parent reference
joelvh Aug 10, 2017
273b78d
Add `superclass` helper to get inherited values for STI
joelvh Aug 10, 2017
2d8004a
Use inherited class and association names
joelvh Aug 10, 2017
be3ddd4
Standardize same options on group members as on groups
joelvh Aug 10, 2017
1f1ecd7
DRY configuring defaults
joelvh Aug 10, 2017
9d65209
Return nothing if child association name not specified
joelvh Aug 10, 2017
2b82b3b
Don't delegate methods to create records
joelvh Aug 10, 2017
2310dd2
Updated README with polymorphic and STI documentation
joelvh Aug 10, 2017
566e7ae
Added tests for disabling default associations
joelvh Aug 10, 2017
7b8ee8f
Add block to extend `has_many`
joelvh Aug 10, 2017
5a3ade8
Indicate 4.1+ support
joelvh Aug 11, 2017
1475205
Updated README
joelvh Aug 11, 2017
434b6e6
Revert ActiveRecord configuration to inline for clarity
joelvh Aug 11, 2017
9ac8b4d
Replicated configuration setup from ActiveRecord to Mongoid
joelvh Aug 11, 2017
2ae566b
Renamed "merge" methods for clarity
joelvh Aug 11, 2017
bee094f
Removed redundant scope merge
joelvh Aug 11, 2017
ca534e0
Disable `appraisal` gem because it causes segfaults - loosen other ge…
joelvh Aug 11, 2017
a6d2a90
Don't add default associations by default, but provide `configure_leg…
joelvh Aug 11, 2017
003fe8f
Updated README
joelvh Aug 11, 2017
b72a143
Consistent naming of `opts`
joelvh Aug 11, 2017
8909726
Cleanup unused code
joelvh Aug 11, 2017
d9305e2
Update generator with default configuration comments
joelvh Aug 11, 2017
48ec17a
Note that groups and members need to be persisted first
joelvh Aug 16, 2017
fb89783
Add method to get membership types for a group/member combo
joelvh Aug 16, 2017
f302c0e
Add `ParentQueryBuilder` methods as extension methods to models
joelvh Aug 17, 2017
8768892
Build extensions in separate files for clarity
joelvh Aug 17, 2017
10c370a
Generate extension modules rather than using helper classes
joelvh Aug 17, 2017
7fba336
Pass extension block to `has_many`
joelvh Aug 17, 2017
d95c594
Continued de-duplication of code with dynamic module creation
joelvh Aug 17, 2017
3ef6176
Renamed `ModelMembershipExtensions` to `ModelExtensions`
joelvh Aug 17, 2017
7b6bc9c
Remove references to old classes
joelvh Aug 17, 2017
7f54082
Fix module naming
joelvh Aug 17, 2017
ec14b70
Added ability to search on multiple membership types (SQL `OR`/`IN(..…
joelvh Aug 17, 2017
e30b732
Fix tests and DSL to allow both group and member implemented on one m…
joelvh Aug 17, 2017
61612d8
Set `class_name` when inferring class from association name (STI)
joelvh Aug 18, 2017
d7ab6fd
Removed Rails 4.1 support
joelvh Aug 18, 2017
0757a3a
Removed unnecessary option
joelvh Aug 18, 2017
5f23bed
Fixes Rails 4.2 bug merging associations
joelvh Aug 19, 2017
6e668c7
Adding should throw validation exception because old version called `…
joelvh Aug 20, 2017
6a19ef0
Fix tests to use specific order of records for PostgreSQL
joelvh Aug 20, 2017
95c4ebb
Fix merging relations that don't go through group memberships
joelvh Aug 21, 2017
3c020ca
Fix test to order comparison for PostgreSQL
joelvh Aug 21, 2017
6469902
Remove db type check
joelvh Aug 21, 2017
76b70c6
Use new helper method
joelvh Aug 21, 2017
88c255a
Check for relation to generate subquery
joelvh Aug 30, 2017
cb5c741
Fixed exclusion path
joelvh Aug 30, 2017
f64a844
Require newer versions of gems
joelvh Aug 30, 2017
3f99967
Simplify generated variable names and clarify attribute references
joelvh Aug 30, 2017
3696802
Fix test that is using a class that wasn't designated as a group member
joelvh Sep 4, 2017
a46ff8e
Fix inverse association references for unsaved records
joelvh Sep 4, 2017
a1edc01
Test for persistence of new records when adding to groups
joelvh Sep 4, 2017
cde2b17
Added brief descriptions to polymorphic classes
joelvh Sep 4, 2017
284df94
Fix MySQL GROUP BY error when sql_mode=only_full_group_by
joelvh Sep 5, 2017
719430d
Add instructions on how to run tests
joelvh Sep 7, 2017
97d3db6
Made further cross-database SQL compatibility fixes for DISTINCT
joelvh Sep 7, 2017
3831368
Add line info for dynamic modules
joelvh Sep 7, 2017
3c7d529
Merge remote-tracking branch 'dwbutler/fix/rails5-named-groups' into …
joelvh Sep 7, 2017
ad62271
Clean membership handling - could add multiple memberships at once
joelvh Sep 8, 2017
ff2c0e2
Update authors
joelvh Sep 10, 2017
3959b50
Fix attempt to join and merge to do so on group model, not group memb…
joelvh Oct 4, 2017
e9b7e62
Join and merge is flawed - removed
joelvh Oct 4, 2017
05035e0
Clarify error source
joelvh Nov 1, 2017
dea8e47
Don't infer association or class name if `:class_name` option is spec…
joelvh Jan 12, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.bundle
.config
.yardoc
gemfiles/vendor
Gemfile.lock
InstalledFiles
_yardoc
Expand Down
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ rvm:
- jruby-9.1.9.0
#- rubinius-3
gemfile:
- gemfiles/rails_4.0.gemfile
- gemfiles/rails_4.1.gemfile
- gemfiles/rails_4.2.gemfile
- gemfiles/rails_5.0.gemfile
- gemfiles/rails_5.1.gemfile
Expand Down
12 changes: 0 additions & 12 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
appraise "rails-4.0" do
gem 'activerecord', "~> 4.0.0"
gem "mongoid", "~> 4.0"
end

appraise "rails-4.1" do
gem 'activerecord', "~> 4.1.0"

gem "mongoid", "~> 4.0"
end

appraise "rails-4.2" do
gem 'activerecord', "~> 4.2.0"

gem "mongoid", "~> 4.0"
end

Expand Down
6 changes: 3 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ end
group :test do
gem "rspec", ">= 3"

gem "database_cleaner", "~> 1.5.3"
gem "combustion", "0.5.5"
gem "database_cleaner", ">= 1.6.1"
gem "combustion", ">= 0.7.0"
gem "appraisal"
gem 'coveralls', require: false
gem "codeclimate-test-reporter", require: nil
Expand All @@ -27,6 +27,6 @@ end

platforms :ruby do
gem "sqlite3"
gem "mysql2", "~> 0.3.11"
gem "mysql2", ">= 0.3.11"
gem "pg"
end
267 changes: 244 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ model? Use named groups instead to add members to named groups such as
## Compatibility

The following ORMs are supported:
* ActiveRecord 4.x, 5.x
* ActiveRecord 4.2+, 5.x
* Mongoid 4.x, 5.x, 6.x

The following Rubies are supported:
Expand All @@ -36,9 +36,9 @@ Or install it yourself as:

$ gem install groupify

### Setup
## Setup

#### Active Record
### Active Record

Execute:

Expand All @@ -63,7 +63,7 @@ class Assignment < ActiveRecord::Base
end
```

#### Mongoid
### Mongoid

Execute:

Expand All @@ -74,40 +74,65 @@ Set up your member models:
```ruby
class User
include Mongoid::Document

groupify :group_member
groupify :named_group_member
end
```

#### Advanced Configuration
## Test Suite

Run the RSpec test suite by installing the `appraisal` gem and dependencies:

$ gem install appraisal
$ appraisal install

And then running tests using `appraisal`:

##### Groupify Model Names
$ appraisal rake

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:
## Advanced Configuration

### Groupify Model Names

The default classes for groups, group members and group memberships are configurable.
The default association name for groups and members is also configurable.
Add the following configuration in `config/initializers/groupify.rb` to change the model names for all classes:

```ruby
Groupify.configure do |config|
config.group_class_name = 'MyCustomGroup'
config.member_class_name = 'MyCustomMember'

# Default to `false` so default associations are not automatically created
config.default_groups_association_name = :groups
config.default_members_association_name = :members

# 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:
#### Backwards-compatible Configuration Defaults

The new default configuration does *not* create default associations or make assumptions about your
group and group member class names. If you would like to retain the *legacy* defaults, you can
utilize the `configure_legacy_defaults!` convenience method.

```ruby
class Member < ActiveRecord::Base
groupify :group_member, group_class_name: 'MyOtherCustomGroup'
Groupify.configure do |config|
config.configure_legacy_defaults!

# These are the legacy defaults configured for you:
# config.group_class_name = 'Group'
# config.member_class_name = 'User'
#
# config.groups_association_name = :groups
# config.members_association_name = :members
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
### Groups: Configuring Group Members

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.
Expand All @@ -119,29 +144,225 @@ class Group < ActiveRecord::Base
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.
In addition to your configuration, Groupify will create a default `members` association.
The default association name can be customized with the `Groupify.default_members_association_name`
setting. If the association name is set to `false`, no default association is created.

The `default_members` option specified in the example above is used to infer the model class when accessing the
default members association (e.g. `members`). Based on the example, `group.members` would return the
users who are members of this group. Note: if `Groupify.default_members_association_name` is set to `false`
then the name specified for `default_members` will be used as the default members association name for this class
(e.g. `group.users` in this case). If that were the case, you would not need to specify `members: [:users]` because
it would be overwritten with a new default association.

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.
If you are using single table inheritance (STI), child classes inherit the member associations
of the parent. If your child class needs to add more members, use the `has_members` method. You can specify
the same options that `has_many through` accepts to customize the association as you please. Note: when using inheritance,
it is recommended to specify the `source_type` option with the base class when you run into circular dependency
issues with your groups and members.

Example:

```ruby
class Organization < Group
has_members :offices, :equipment
end

class InternationalOrganization < Organization
has_member :offices, class_name: 'CustomOfficeClass'
has_member :equipment, class_name: 'CustomEquipmentClass'

# mitigate issues with inheritance and circular dependencies with groups and members
has_member :specific_equipment, class_name: 'SpecificEquipment', source_type: 'CustomEquipmentClass'
end
```

Mongoid works the same way by creating Mongoid relations.

## Usage
With polymorphic groups, the `default_members` option specifies the association
on the group to which members should be added. When specifying individual `has_member`
options, `default_members: true` indicates the association is the one to add new
members to. (If the `default_members` is not specified and the `members` association
does not exist, adding users to subclasses of a group can cause a
`ActiveRecord::AssociationTypeMismatch` exception.)

### Create groups and add members
Example:

```ruby
class GroupBase < ActiveRecord::Base
self.table_name = "groups"
self.abstract_class = true
end

class Organization < GroupBase
acts_as_group
has_member :users, class_name: 'CustomUserClass', default_members: true
end

org = Organization.create!
user = CustomUserClass.create!

# adds the user to the `ord.users` association
org.add user, as: 'admin'
```

### Group Members: Configuring Groups

Your member class can be configured to create associations for each expected group type.
For example, let's say that your member class will have multiple types of organizations as groups.
The following configuration adds `organizations` and `international_organizations` associations
on the member model:

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

class Organization < Group
has_members :offices, :equipment
end

class InternationalOrganization < Organization
end

class Member < ActiveRecord::Base
groupify :group_member, groups: [:groups, :organizations, :international_organizations], default_groups: :groups
end
```

In addition to your configuration, Groupify will create a default `groups` association.
The default association name can be customized with the `Groupify.default_groups_association_name`
setting.

The `default_groups` option specified in the example above sets the model type when accessing the
default groups association (e.g. `groups`). Based on the example, `member.groups` would return the
groups the member has a membership to. Note: if `Groupify.default_groups_association_name` is set to `false`
then the `default_groups` name will be used as the default members association name for this class
(e.g. `member.groups` in this case).

Note: the `group_class_name` option can be specified as the default group class for backwards-compatibility. However,
unlike the `default_groups` option, a default association will not be created if `Groupify.default_groups_association_name`
is set to `false`.

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

If you are using single table inheritance (STI), child classes inherit the group associations
of the parent. If your child class needs to add more members, use the `has_groups` method. You can specify
the same options that `has_many through` accepts to customize the association as you please. Note: when using inheritance,
it is recommended to specify the `source_type` option with the base class when you run into circular dependency
issues with your groups and members.

Example:

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

class Organization < Group
has_members :offices, :equipment
end

class InternationalOrganization < Organization
end

class Member < ActiveRecord::Base
groupify :group_member

has_group :owned_organizations, class_name: 'Organization'
end
```

### Implementing Group and Group Member on a Single Model (Active Record only)

When a model is designated both as a group and a group member, some things can become ambiguous internally
to Groupify. Usually the context can be inferred. However, when it can't, Groupify assumes that your model
is a member.

For example, if a `Group` can be a member and a group, the following will return groups:

```ruby
class Group < ActiveRecord::Base
groupify :group
groupify :group_member
end

member = Group.create!
group = Group.create!

group.add member, as: :owner

# This will return members who are in groups with the given membership type
Group.as(:owner) # [member]
```

### Polymorphic Groups and Members (Active Record Only)

When you configure multiple models as group or member, you may need to retrieve all groups or members,
particularly if they are not single-table inheritance models. When your models are distributed across
multiple tables, Groupify provides the ability to access all groups or users with the `group.polymorphic_members`
and `member.polymorphic_groups` helper methods. This returns an `Enumerable` collection of groups or members.

Note: this collection effectively retrieves the group memberships and includes the `group_membership.group` or
`group_membership.member` to minimize N+1 queries, then returns only the groups or members from the group memberships
results.

You can filter on membership type:

```ruby
# member example
user.polymorphic_groups.as(:manager)

# group example
group.polymorphic_members.as(:manager)
```

If you want to treat the collection like a scope, you can pass in a block which modifies the
criteria for retrieving the group memberships.

```ruby
# member example
user.polymorphic_groups{where(group_type: 'CustomGroup')}

# group example
group.polymorphic_members{where(member_type: 'CustomMember')}
```

If you want to treat the collection like an association, you can add groups to the collection and
group memberships will be created.

```ruby
# member example
group = Group.new
user.polymorphic_groups << group
user.in_group?(group) # true
# equivalent to:
user.groups << group
user.in_group?(group) # true

# group example
user = User.new
group.polymorphic_members << user
user.in_group?(group) # true
# equivalent to:
group.members << user
user.in_group?(group) # true
```

See _Usage_ below for additional functionality, such as how to specify membership type

## Usage

### Create groups and add members

```ruby
# NOTE: ActiveRecord groups and members must be persisted before creating memberships.
group = Group.create!
user = User.create!

user.groups << group
# or
Expand Down
Loading