Class: Parse::Role

Inherits:
Object show all
Defined in:
lib/parse/model/classes/role.rb,
lib/parse/stack/generators/templates/model_role.rb

Overview

This class represents the data and columns contained in the standard Parse ‘_Role` collection. Roles allow the an application to group a set of User records with the same set of permissions, so that specific records in the database can have ACLs related to a role than trying to add all the users in a group.

The default schema for Role is as follows:

class Parse::Role < Parse::Object
   # See Parse::Object for inherited properties...

   property :name

   # A role may have child roles.
   has_many :roles, through: :relation

   # The set of users who belong to this role.
   has_many :users, through: :relation
end

Examples:

Creating and managing roles

# Create an admin role
admin = Parse::Role.create(name: "Admin")

# Add users to the role
admin.add_user(user1)
admin.add_users(user2, user3)
admin.save

# Create role hierarchy. Parse Server's _Role semantics: when role X
# holds role Y in its `roles` relation, USERS OF Y INHERIT X'S
# PERMISSIONS — not the other way around. So if you want Admins to
# have everything Moderators can do, you must put Admin into the
# Moderator role's `roles` relation:
moderator = Parse::Role.create(name: "Moderator")
moderator.add_child_role(admin)  # Admins inherit Moderator permissions
moderator.save

# Query users in role (including child roles whose users implicitly
# have this role through Parse's inheritance):
all_users = moderator.all_users  # includes Admin's users transitively

See Also:

Constant Summary

Constants inherited from Object

Object::BUILTIN_PARSE_CLASS_NAMES, Object::IDENTIFICATION_FIELDS, Object::VALID_ACL_POLICIES

Constants included from Core::Schema

Core::Schema::DEFAULT_PUBLIC_CLP, Core::Schema::SCHEMA_READONLY_CLASSES

Constants included from Core::Describe

Core::Describe::ALL_SECTIONS, Core::Describe::CORE_FIELD_KEYS, Core::Describe::LOCAL_SECTIONS, Core::Describe::NETWORK_SECTIONS

Constants included from Core::Indexing

Core::Indexing::MAX_INDEXES_PER_COLLECTION, Core::Indexing::PARSE_MANAGED_ARRAY_FIELDS, Core::Indexing::SENSITIVE_FIELDS

Constants included from Core::SearchIndexing

Core::SearchIndexing::ALLOWED_INDEX_TYPES, Core::SearchIndexing::INDEX_NAME_PATTERN

Constants included from Core::Fetching

Core::Fetching::NON_SERIALIZABLE_IVARS

Constants included from Core::ParseReference

Core::ParseReference::OBJECT_ID_LENGTH, Core::ParseReference::SEPARATOR

Constants included from Core::FieldGuards

Core::FieldGuards::GUARD_MODES

Constants included from Properties

Properties::BASE, Properties::BASE_FIELD_MAP, Properties::BASE_KEYS, Properties::CORE_FIELDS, Properties::DELETE_OP, Properties::PROTECTED_INITIALIZE_KEYS, Properties::PROTECTED_MASS_ASSIGNMENT_KEYS, Properties::TYPES

Constants inherited from Pointer

Pointer::ATTRIBUTES, Pointer::OBJECT_ID_FORMAT

Constants inherited from Model

Model::CLASS_AUDIENCE, Model::CLASS_INSTALLATION, Model::CLASS_JOB_SCHEDULE, Model::CLASS_JOB_STATUS, Model::CLASS_PRODUCT, Model::CLASS_PUSH_STATUS, Model::CLASS_ROLE, Model::CLASS_SCHEMA, Model::CLASS_SESSION, Model::CLASS_USER, Model::ID, Model::KEY_CLASS_NAME, Model::KEY_CREATED_AT, Model::KEY_OBJECT_ID, Model::KEY_UPDATED_AT, Model::OBJECT_ID, Model::TYPE_ACL, Model::TYPE_BYTES, Model::TYPE_DATE, Model::TYPE_FIELD, Model::TYPE_FILE, Model::TYPE_GEOPOINT, Model::TYPE_NUMBER, Model::TYPE_OBJECT, Model::TYPE_POINTER, Model::TYPE_POLYGON, Model::TYPE_RELATION

Instance Attribute Summary collapse

Attributes inherited from Object

#acl, #created_at, #id, #updated_at

Attributes inherited from Pointer

#id, #parse_class

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Object

#[], #[]=, #__type, #_resolve_acl_owner_id, #_resolve_default_acl, #acl_changed?, acl_owner_field, acl_policy, acl_policy_setting, #acl_was, #acl_will_change!, #after_create, #after_destroy, #after_save, #after_update, #after_validation, #apply_defaults!, #around_create, #around_destroy, #around_save, #around_update, #around_validation, #as_json, #autofetch_disabled?, #before_create, #before_destroy, #before_save, #before_update, #before_validation, build, #changed, #changed?, class_permissions, #clear_attribute_change!, #clear_changes!, #clear_partial_fetch_state!, default_acls, describe_access, #disable_autofetch!, #enable_autofetch!, #existed?, fetch_clp, #fetched?, #fetched_keys, #fetched_keys=, #field_was_fetched?, #filter_for_user, filter_results_for_user, #fully_fetched?, #has?, #has_selective_keys?, #initialize, #keys, master_only_class!, #nested_fetched_keys, #nested_fetched_keys=, #nested_keys_for, #new?, #parse_class, #partially_fetched?, #persisted?, pointer, #pretty, private_acl!, protect_fields, #reload!, roles_for_user, #rollback!, #run_after_create_callbacks, #run_after_delete_callbacks, #run_after_save_callbacks, #schema, set_class_access, set_clp, set_default_acl, set_default_clp, set_read_user_fields, set_write_user_fields, #twin, unlistable_class!, update_clp!, #updates, #valid?, #validate!, webhook, webhook_function

Methods included from Core::Querying

#all, #count, #count_distinct, #cursor, #distinct, #each, #find, #find_cached, #first, #last_updated, #latest, #literal_where, #newest, #oldest, #query, #scope, #subscribe

Methods included from Core::Schema

#_default_class_level_permissions_for_upgrade, #auto_upgrade!, #create_schema, #fetch_schema, #reset_clp!, #schema, #update_schema

Methods included from Core::Describe

#describe

Methods included from Core::Indexing

#apply_indexes!, #indexes_plan, #mongo_geo_index, #mongo_index, #mongo_index_declarations, #mongo_relation_index

Methods included from Core::SearchIndexing

#apply_search_indexes!, #mongo_search_index, #mongo_search_index_declarations, #search_indexes_plan

Methods included from Agent::MetadataDSL

#agent_description, #agent_methods, included, #property_descriptions, #property_enum_descriptions

Methods included from Core::Actions

#_deleted?, #change_requests, #changes_applied!, #changes_payload, #create, #destroy, #destroy_request, #op_add!, #op_add_relation!, #op_add_unique!, #op_destroy!, #op_increment!, #op_remove!, #op_remove_relation!, #operate_field!, #prepare_save!, #relation_change_operations, #save, #save!, #set_attributes!, #update, #update!, #update_relations, #uri_path

Methods included from Core::Fetching

#autofetch!, #fetch, #fetch!, #fetch_cache!, #fetch_json, #fetch_object, #prepare_for_dirty_tracking!

Methods included from Associations::HasMany

has_many, #relation_changes?, #relation_updates, #relations

Methods included from Associations::BelongsTo

belongs_to, #key?

Methods included from Associations::HasOne

has_one

Methods included from Core::ParseReference

format, generate_object_id, parse

Methods included from Core::FieldGuards

#apply_field_guards!

Methods included from Core::EnhancedChangeTracking

included

Methods included from Properties

#apply_attributes!, #attribute_changes?, #attribute_updates, #attributes, #attributes=, #field_map, #fields, #format_operation, #format_value

Methods inherited from Pointer

#==, #[], #[]=, #__type, #attributes, #className, #fetch, #fetch_cache!, #fetch_json, #fetch_object, #fetched?, #hash, #initialize, #json_hash, #method_missing, #pointer, #pointer?, #present?, #respond_to_missing?, #sig

Methods inherited from Model

#dirty?, find_class

Methods included from Client::Connectable

#client

Constructor Details

This class inherits a constructor from Parse::Object

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Parse::Pointer

Instance Attribute Details

#nameString

Returns the name of this role.

Returns:

  • (String)

    the name of this role.



54
# File 'lib/parse/model/classes/role.rb', line 54

property :name

Class Method Details

.all_for_user(user, max_depth: 10, master: false, as: nil) ⇒ Set<String>

Note:

When neither ‘master:` nor `as:` is supplied, the mongo-direct fast path is skipped; the method falls through to the Parse-Server walk (`Parse::Role.all(users: user_pointer)`) which goes through the default Parse::Client. This preserves backward compatibility for the many SDK-internal call sites that compose ACL scopes (acl_scope, atlas_search session, query/constraints) — none of those have a caller scope to forward. The fast path is opt-in for performance-conscious callers that can supply explicit authorization.

Return the transitive upward closure of role names a user inherits permissions from.

Parse Server _Role inheritance: when role X holds role Y in its roles relation, users of Y inherit X‘s permissions. So given a user U, the permission set is built by:

1. Querying for every role +D+ where +U+ is a direct member
   (+_Role.users+ contains +U+).
2. For each direct role +D+, walking upward to every role
   +P+ that lists +D+ in its +roles+ relation. Repeat until
   no new parents are found.

This is the correct primitive for building _rperm predicates (e.g., ACLReadableByConstraint, ACLWritableByConstraint, and the Atlas Search ACL $match injection). The legacy walk via #all_child_roles on the user’s direct roles traverses the wrong direction and over-grants — it returns roles whose users include the input user through inheritance, not the roles the input user inherits permissions from.

Cycle-safe: a visited-id set guards against pathological _Role.roles cycles (e.g. A→B→A).

Examples:

names = Parse::Role.all_for_user(user, master: true)  # admin/analytics
names = Parse::Role.all_for_user(user, as: current_user)  # scope-checked

Parameters:

  • user (Parse::User, Parse::Pointer, String, nil)

    the user to expand. A Parse::Pointer must be on the _User class. A String is treated as a _User objectId. nil returns an empty set (anonymous).

  • max_depth (Integer) (defaults to: 10)

    maximum BFS depth (default: 10).

  • master (Boolean) (defaults to: false)

    when true, opt in to the mongo-direct fast path under master-mode (bypasses ‘_Role` CLP). Use for admin/analytics code paths that legitimately need a master-scope view of the role graph.

  • as (Parse::User, Parse::Pointer, nil) (defaults to: nil)

    when supplied, opt in to the mongo-direct fast path under the caller’s scope (subject to ‘_Role` CLP). The scope is forwarded verbatim to MongoDB.role_names_for_user; CLP denial raises CLPScope::Denied.

Returns:

  • (Set<String>)

    role names (no role: prefix) the user transitively inherits permissions from, including direct memberships. Empty set for anonymous or no-membership users.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/parse/model/classes/role.rb', line 169

def all_for_user(user, max_depth: 10, master: false, as: nil)
  names = Set.new
  return names if user.nil? || max_depth <= 0

  user_pointer = role_lookup_pointer_for(user)
  return names if user_pointer.nil?

  # The fast path is opt-in. When neither `master:` nor `as:` is
  # supplied, skip it entirely — the underlying mongo helper
  # would raise ArgumentError, and we don't want to surprise
  # the many backward-compat call sites (acl_scope.resolve_for_user,
  # atlas_search Session.role_names_for, query/constraints' ACL
  # constraint building, agent default-scope composition) that
  # have no scope to forward.
  if master == true || !as.nil?
    fast_path_result = all_for_user_mongo_fast_path(
      user_pointer.id, max_depth, master: master, as: as,
    )
    if fast_path_result.is_a?(Set)
      ActiveSupport::Notifications.instrument(
        "parse.role.expand",
        direction: :forward, target_id: user_pointer.id,
        depth: max_depth, source: :mongo_direct,
        result_count: fast_path_result.size,
      )
      return fast_path_result
    end
  end

  begin
    direct_roles = Parse::Role.all(users: user_pointer)
  rescue
    return names
  end

  result = expand_inheritance_upward(direct_roles, max_depth: max_depth)
  ActiveSupport::Notifications.instrument(
    "parse.role.expand",
    direction: :forward, target_id: user_pointer.id,
    depth: max_depth, source: :parse_server,
    result_count: result.size,
  )
  result
end

.all_namesArray<String>

Get all role names in the system.

Returns:



103
104
105
# File 'lib/parse/model/classes/role.rb', line 103

def all_names
  query.results.map(&:name)
end

.exists?(role_name) ⇒ Boolean

Check if a role with the given name exists.

Parameters:

  • role_name (String)

    the name to check.

Returns:

  • (Boolean)

    true if role exists.



110
111
112
# File 'lib/parse/model/classes/role.rb', line 110

def exists?(role_name)
  query(name: role_name).count > 0
end

.expand_inheritance_upward(starting_roles, max_depth: 10) ⇒ Set<String>

Walk upward from a starting frontier of Parse::Role objects through the _Role.roles inverse relation, collecting every role name reachable. Used by all_for_user (frontier = the user’s direct roles) and #all_parent_role_names (frontier = the role itself).

The starting frontier is INCLUDED in the returned set, because the semantics is “every role name whose presence in _rperm grants access” — direct membership counts.

Parameters:

  • starting_roles (Array<Parse::Role>)

    roles to begin the upward traversal from.

  • max_depth (Integer) (defaults to: 10)

    maximum BFS depth.

Returns:

  • (Set<String>)

    role names (no role: prefix) including the starting frontier and every transitive parent.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/parse/model/classes/role.rb', line 265

def expand_inheritance_upward(starting_roles, max_depth: 10)
  names = Set.new
  visited_ids = Set.new
  frontier = []

  Array(starting_roles).each do |role|
    next if role.nil? || role.id.nil?
    next if visited_ids.include?(role.id)
    visited_ids << role.id
    names << role.name if role.respond_to?(:name) && role.name.present?
    frontier << role
  end

  depth = 0
  while frontier.any? && depth < max_depth
    next_frontier = []
    frontier.each do |role|
      next if role.nil? || role.id.nil?
      begin
        parents = Parse::Role.all(roles: role)
      rescue
        next
      end
      parents.each do |parent|
        next if parent.nil? || parent.id.nil?
        next if visited_ids.include?(parent.id)
        visited_ids << parent.id
        names << parent.name if parent.respond_to?(:name) && parent.name.present?
        next_frontier << parent
      end
    end
    frontier = next_frontier
    depth += 1
  end

  names
end

.find_by_name(role_name) ⇒ Parse::Role?

Find a role by its name.

Examples:

admin = Parse::Role.find_by_name("Admin")

Parameters:

  • role_name (String)

    the name of the role to find.

Returns:

  • (Parse::Role, nil)

    the role if found, nil otherwise.



81
82
83
# File 'lib/parse/model/classes/role.rb', line 81

def find_by_name(role_name)
  query(name: role_name).first
end

.find_or_create(role_name, acl: nil) ⇒ Parse::Role

Find or create a role by name.

Examples:

admin = Parse::Role.find_or_create("Admin")

Parameters:

  • role_name (String)

    the name of the role.

  • acl (Parse::ACL) (defaults to: nil)

    optional ACL to set on creation.

Returns:

  • (Parse::Role)

    the existing or newly created role.



91
92
93
94
95
96
97
98
99
# File 'lib/parse/model/classes/role.rb', line 91

def find_or_create(role_name, acl: nil)
  role = find_by_name(role_name)
  return role if role

  role = new(name: role_name)
  role.acl = acl if acl
  role.save
  role
end

Instance Method Details

#add_child_role(role) ⇒ self

Add a child role to this role’s hierarchy.

**The method name is misleading — prefer #grant_capabilities_to! or #inherits_capabilities_from!.** ‘add_child_role` mutates the receiver’s ‘roles` relation; per Parse Server semantics, putting role Y in role X’s ‘roles` relation grants X’s capabilities to users-of-Y. The “child” terminology has the inheritance direction exactly inverted from intuitive org-chart reading. Retained for backward compatibility and as the low-level structural primitive; new callers should use the direction-explicit semantic methods.

IMPORTANT — Parse Server _Role inheritance semantics: when role X holds role Y in its ‘roles` relation, **users of Y inherit X’s permissions** (not the other way around). So calling admin.add_child_role(moderator) does NOT grant Moderator’s capabilities to Admin; it grants Admin’s capabilities to every Moderator user — privilege escalation.

If you want Admins to have everything Moderators can do, you need to add ADMIN to MODERATOR’s roles relation:

moderator.add_child_role(admin)  # Admins now have Moderator capabilities

Direction-explicit replacements:

admin.inherits_capabilities_from!(moderator)   # admin perspective
moderator.grant_capabilities_to!(admin)        # moderator perspective

Both bang variants auto-save and return self.

Parameters:

  • role (Parse::Role)

    the role to add to this role’s ‘roles` relation.

Returns:

  • (self)

    returns self for chaining.

Raises:

  • (ArgumentError)

    when role is self (a self-loop in the ‘_Role.roles` relation produces an infinite recursion on lookup and serves no permission purpose).



397
398
399
400
401
# File 'lib/parse/model/classes/role.rb', line 397

def add_child_role(role)
  assert_not_self_reference!(role, :add_child_role)
  roles.add(role)
  self
end

#add_child_roles(*role_list) ⇒ self

Add multiple child roles to this role’s hierarchy. See #add_child_role for the inheritance-direction caveat.

Parameters:

Returns:

  • (self)

    returns self for chaining.

Raises:

  • (ArgumentError)

    when any entry in role_list is self.



408
409
410
411
412
413
# File 'lib/parse/model/classes/role.rb', line 408

def add_child_roles(*role_list)
  flat = role_list.flatten
  flat.each { |r| assert_not_self_reference!(r, :add_child_roles) }
  roles.add(flat)
  self
end

#add_user(user) ⇒ self

Add a single user to this role.

Examples:

role.add_user(user).save

Parameters:

Returns:

  • (self)

    returns self for chaining.



333
334
335
336
# File 'lib/parse/model/classes/role.rb', line 333

def add_user(user)
  users.add(user)
  self
end

#add_users(*user_list) ⇒ self

Add multiple users to this role.

Examples:

role.add_users(user1, user2, user3).save

Parameters:

Returns:

  • (self)

    returns self for chaining.



343
344
345
346
# File 'lib/parse/model/classes/role.rb', line 343

def add_users(*user_list)
  users.add(user_list.flatten)
  self
end

#all_child_roles(max_depth: 10, visited: Set.new) ⇒ Array<Parse::Role>

Get all child roles recursively. Cycle-safe; see #all_users.

Parameters:

  • max_depth (Integer) (defaults to: 10)

    maximum recursion depth.

  • visited (Set) (defaults to: Set.new)

    internal cycle-detection accumulator.

Returns:



699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/parse/model/classes/role.rb', line 699

def all_child_roles(max_depth: 10, visited: Set.new)
  return [] if max_depth <= 0
  return [] if id.nil? || visited.include?(id)
  visited << id

  direct_children = roles.all
  nested_children = direct_children.flat_map do |child|
    child.all_child_roles(max_depth: max_depth - 1, visited: visited)
  end

  (direct_children + nested_children).uniq { |r| r.id }
end

#all_parent_role_names(max_depth: 10) ⇒ Set<String>

Get the set of role names whose presence in a _rperm array grants access to this role’s members. That’s the role itself plus every role P that lists this role in its roles relation, transitively upward — because users of this role inherit P‘s permissions under Parse Server’s role-inheritance semantics (see #add_child_role).

The instance-side analogue to all_for_user; the two share an internal BFS via expand_inheritance_upward. Use this method when compiling an ACL predicate around a role argument, e.g. :ACL.readable_by => admin_role: the role itself contributes “role:Admin”, and any role whose .roles relation contains admin_role also grants Admins access through inheritance.

The legacy #all_child_roles walk is NOT a substitute. Child roles inherit FROM this role (their members get this role’s capabilities), so child-role names in _rperm would not grant this role’s members anything — the walk traverses the wrong direction for ACL composition.

Examples:

permission_strings = admin.all_parent_role_names.map { |n| "role:#{n}" }

Parameters:

  • max_depth (Integer) (defaults to: 10)

    maximum BFS depth (default: 10).

Returns:

  • (Set<String>)

    role names (no role: prefix) including self.name and every transitive parent.



691
692
693
# File 'lib/parse/model/classes/role.rb', line 691

def all_parent_role_names(max_depth: 10)
  Parse::Role.expand_inheritance_upward([self], max_depth: max_depth)
end

#all_users(max_depth: 10, visited: Set.new, master: false, as: nil) ⇒ Array<Parse::User>

Note:

When neither ‘master:` nor `as:` is supplied, the mongo-direct fast path is skipped; the method falls through to the Parse-Server walk through the per-relation query interface, which goes through the default Parse::Client.

Get all users belonging to this role, including users from child roles recursively.

Cycle-safe: a visited set guards against pathological _Role.roles cycles (e.g. A→B→A) that would otherwise cause exponential per-node query fan-out.

Examples:

all_users = admin_role.all_users(master: true)
visible = admin_role.all_users(as: current_user)

Parameters:

  • max_depth (Integer) (defaults to: 10)

    maximum recursion depth.

  • visited (Set) (defaults to: Set.new)

    internal cycle-detection accumulator.

  • master (Boolean) (defaults to: false)

    when true, opt in to the mongo-direct fast path under master-mode. The follow-up ‘_User` fetch also runs unscoped — used for admin/analytics paths that need a master-key view of every member.

  • as (Parse::User, Parse::Pointer, nil) (defaults to: nil)

    when supplied, opt in to the mongo-direct fast path under the caller’s scope. The ‘_Role` CLP is checked on entry, the `_User` `_rperm` allow-set is folded into the join sub-pipeline (so the fast path returns only members the scope is allowed to read), and the follow-up `Parse::MongoDB.aggregate` call to hydrate the user rows runs under the same scope (so `_User` ACL fires for both the join filter AND the post-fetch hydration).

Returns:



557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# File 'lib/parse/model/classes/role.rb', line 557

def all_users(max_depth: 10, visited: Set.new, master: false, as: nil)
  return [] if max_depth <= 0
  return [] if id.nil? || visited.include?(id)

  # The fast path is opt-in (same rationale as {.all_for_user}).
  if master == true || !as.nil?
    fast_path = all_users_mongo_fast_path(max_depth, master: master, as: as)
    if fast_path.is_a?(Array)
      ActiveSupport::Notifications.instrument(
        "parse.role.expand",
        direction: :reverse, target_id: id, depth: max_depth,
        source: :mongo_direct, result_count: fast_path.size,
      )
      return fast_path
    end
  end

  visited << id

  direct_users = users.all

  child_roles = roles.all
  child_users = child_roles.flat_map do |child_role|
    child_role.all_users(max_depth: max_depth - 1, visited: visited)
  end

  result = (direct_users + child_users).uniq { |u| u.id }
  ActiveSupport::Notifications.instrument(
    "parse.role.expand",
    direction: :reverse, target_id: id, depth: max_depth,
    source: :parse_server, result_count: result.size,
  )
  result
end

#child_roles_countInteger

Get the count of direct child roles.

Returns:

  • (Integer)

    number of direct child roles.



720
721
722
# File 'lib/parse/model/classes/role.rb', line 720

def child_roles_count
  roles.query.count
end

#grant_capabilities_to(grantee) ⇒ self

Grant this role’s capabilities to the given role’s users. Reads as: “users with grantee now have self‘s capabilities.” Equivalent to self.add_child_role(grantee) but unambiguous about the direction of inheritance.

Non-saving — the caller must call self.save to persist. See #grant_capabilities_to! for the auto-saving variant.

Examples:

moderator.grant_capabilities_to(admin).save
# → Admin users can now do anything Moderator users can

Parameters:

  • grantee (Parse::Role)

    the role whose users will inherit this role’s permissions.

Returns:

  • (self)

    returns self for chaining.



444
445
446
447
448
# File 'lib/parse/model/classes/role.rb', line 444

def grant_capabilities_to(grantee)
  assert_not_self_reference!(grantee, :grant_capabilities_to)
  roles.add(grantee)
  self
end

#grant_capabilities_to!(grantee) ⇒ self

Auto-saving variant of #grant_capabilities_to. Performs the relation mutation AND persists self in one call. Returns self consistently so the caller can chain or store the result without tracking which object was mutated. Prefer this in tests and one-shot scripts where batching multiple mutations isn’t needed.

Examples:

moderator.grant_capabilities_to!(admin)
# → Admin users can now do anything Moderator users can. Persisted.

Parameters:

  • grantee (Parse::Role)

    the role whose users will inherit this role’s permissions.

Returns:

  • (self)

    the mutated and persisted self.

Raises:



462
463
464
465
466
# File 'lib/parse/model/classes/role.rb', line 462

def grant_capabilities_to!(grantee)
  grant_capabilities_to(grantee)
  save!
  self
end

#has_child_role?(role) ⇒ Boolean

Check if a role is a direct child of this role.

Parameters:

Returns:

  • (Boolean)

    true if role is a direct child.



524
525
526
527
# File 'lib/parse/model/classes/role.rb', line 524

def has_child_role?(role)
  return false unless role.is_a?(Parse::Role) && role.id.present?
  roles.query.where(objectId: role.id).count > 0
end

#has_user?(user) ⇒ Boolean

Check if a user belongs to this role (direct membership only).

Parameters:

Returns:

  • (Boolean)

    true if user is a direct member.



516
517
518
519
# File 'lib/parse/model/classes/role.rb', line 516

def has_user?(user)
  return false unless user.is_a?(Parse::User) && user.id.present?
  users.query.where(objectId: user.id).count > 0
end

#inherits_capabilities_from(source) ⇒ Parse::Role

Inverse spelling of #grant_capabilities_to: “this role’s users inherit source‘s capabilities”. Performs the relation mutation on source, not on self.

**Save target.** The mutation lives on source.roles. To persist, the caller must save source, NOT self. This asymmetry exists because Parse Server stores the relation on the role that holds the roles list, and that role is source. The non-bang form is retained for callers that need to batch multiple mutations on source before a single save; prefer #inherits_capabilities_from! for the one-shot case where the auto-save matches intent.

Examples:

Non-saving (must save source separately)

admin.inherits_capabilities_from(moderator)
moderator.save

Auto-saving via the bang variant

admin.inherits_capabilities_from!(moderator)
# → Admin users can now do anything Moderator users can. Persisted.

Parameters:

  • source (Parse::Role)

    the role whose capabilities this role’s users acquire.

Returns:

  • (Parse::Role)

    the source role (caller still needs to .save it if not using the bang variant).



489
490
491
492
493
# File 'lib/parse/model/classes/role.rb', line 489

def inherits_capabilities_from(source)
  assert_not_self_reference!(source, :inherits_capabilities_from)
  source.roles.add(self)
  source
end

#inherits_capabilities_from!(source) ⇒ self

Auto-saving variant of #inherits_capabilities_from. Performs the mutation on source.roles AND saves source for you, then returns self so the caller can keep working with the role they called the method on. Resolves the most common stumbling block with #inherits_capabilities_from: the “save target” asymmetry.

Examples:

admin.inherits_capabilities_from!(moderator)
# → Admin users can now do anything Moderator users can. Persisted.

Parameters:

  • source (Parse::Role)

    the role whose capabilities this role’s users acquire.

Returns:

  • (self)

    the role that now inherits (caller’s original receiver).

Raises:



507
508
509
510
511
# File 'lib/parse/model/classes/role.rb', line 507

def inherits_capabilities_from!(source)
  inherits_capabilities_from(source)
  source.save!
  self
end

#remove_child_role(role) ⇒ self

Remove a child role from this role’s hierarchy.

Parameters:

Returns:

  • (self)

    returns self for chaining.



418
419
420
421
# File 'lib/parse/model/classes/role.rb', line 418

def remove_child_role(role)
  roles.remove(role)
  self
end

#remove_child_roles(*role_list) ⇒ self

Remove multiple child roles from this role’s hierarchy.

Parameters:

Returns:

  • (self)

    returns self for chaining.



426
427
428
429
# File 'lib/parse/model/classes/role.rb', line 426

def remove_child_roles(*role_list)
  roles.remove(role_list.flatten)
  self
end

#remove_user(user) ⇒ self

Remove a single user from this role.

Parameters:

Returns:

  • (self)

    returns self for chaining.



351
352
353
354
# File 'lib/parse/model/classes/role.rb', line 351

def remove_user(user)
  users.remove(user)
  self
end

#remove_users(*user_list) ⇒ self

Remove multiple users from this role.

Parameters:

Returns:

  • (self)

    returns self for chaining.



359
360
361
362
# File 'lib/parse/model/classes/role.rb', line 359

def remove_users(*user_list)
  users.remove(user_list.flatten)
  self
end

#rolesRelationCollectionProxy<Role>

This attribute is mapped as a ‘has_many` Parse relation association with the Parse::Role class, as roles can be associated with multiple child roles to support role inheritance. The roles Parse relation provides a mechanism to create a hierarchical inheritable types of permissions by assigning child roles.

Returns:



60
# File 'lib/parse/model/classes/role.rb', line 60

has_many :roles, through: :relation

#total_users_countInteger

Get the total count of users including child roles.

Returns:

  • (Integer)

    total user count in hierarchy.



726
727
728
# File 'lib/parse/model/classes/role.rb', line 726

def total_users_count
  all_users.count
end

#usersRelationCollectionProxy<User>

This attribute is mapped as a ‘has_many` Parse relation association with the User class.

Returns:



63
# File 'lib/parse/model/classes/role.rb', line 63

has_many :users, through: :relation

#users_countInteger

Get the count of direct users in this role.

Returns:

  • (Integer)

    number of direct users.



714
715
716
# File 'lib/parse/model/classes/role.rb', line 714

def users_count
  users.query.count
end