Class: Parse::Audience

Inherits:
Object
  • Object
show all
Defined in:
lib/parse/model/classes/audience.rb

Overview

Note:

_Audience is hardcoded master-key-only at Parse Server's REST layer (SharedRest.js). CLP changes via Object.set_clp have no effect — manage audiences from a master-key client or expose them through a Cloud Code function.

This class represents the data and columns contained in the standard Parse _Audience collection. Audiences are pre-defined groups of installations that can be targeted for push notifications. They store query constraints that define which installations belong to the audience.

Audiences are useful for:

  • Reusable push targets (e.g., "VIP Users", "Beta Testers")
  • A/B testing different user segments
  • Marketing campaigns to specific demographics

== Caching

Audience queries are cached by default to improve push notification performance. The cache has a configurable TTL (default: 5 minutes).

The default schema for the Audience class is as follows: class Parse::Audience < Parse::Object # See Parse::Object for inherited properties...

 property :name
 property :query, :object  # The Installation query constraints

end

Examples:

Configure cache TTL

Parse::Audience.cache_ttl = 600  # 10 minutes

Clear the cache

Parse::Audience.clear_cache!

Bypass cache for a specific lookup

audience = Parse::Audience.find_by_name("VIP Users", cache: false)

Creating an audience

audience = Parse::Audience.new(
  name: "iOS VIP Users",
  query: { "deviceType" => "ios", "vip" => true }
)
audience.save

Targeting an audience with push

Parse::Push.new
  .to_audience("iOS VIP Users")
  .with_alert("Exclusive offer!")
  .send!

See Also:

Constant Summary collapse

DEFAULT_CACHE_TTL =

Default cache TTL in seconds (5 minutes)

300

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.cache_ttlObject



70
71
72
# File 'lib/parse/model/classes/audience.rb', line 70

def cache_ttl
  @cache_ttl ||= DEFAULT_CACHE_TTL
end

Instance Attribute Details

#nameString

The display name of this audience.

Returns:

  • (String)

    The audience name.



155
# File 'lib/parse/model/classes/audience.rb', line 155

property :name

#queryActiveSupport::HashWithIndifferentAccess?

The query constraints as a Hash (decoded from the stored JSON string).

Returns:

  • (ActiveSupport::HashWithIndifferentAccess, nil)


170
# File 'lib/parse/model/classes/audience.rb', line 170

property :query_json, :string, field: :query

Class Method Details

.cache_fetch(name, cache: true) ⇒ Parse::Audience?

Get an audience from cache or fetch from server

Parameters:

  • name (String)

    the audience name

  • cache (Boolean) (defaults to: true)

    whether to use cache (default: true)

Returns:



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/parse/model/classes/audience.rb', line 87

def cache_fetch(name, cache: true)
  return find_by_name_uncached(name) unless cache

  cache_mutex.synchronize do
    @audience_cache ||= {}
    @cache_timestamps ||= {}

    # Cleanup expired entries periodically to prevent memory growth
    cleanup_expired_cache_entries

    cached = @audience_cache[name]
    timestamp = @cache_timestamps[name]

    # Check if cache is valid
    if timestamp && (Time.now.to_i - timestamp) < cache_ttl
      return cached
    end

    # Fetch and cache (fetch happens inside lock - acceptable for short TTL cache)
    audience = find_by_name_uncached(name)
    @audience_cache[name] = audience
    @cache_timestamps[name] = Time.now.to_i

    audience
  end
end

.cache_mutexMutex

Thread-safe mutex for cache operations

Returns:

  • (Mutex)


125
126
127
# File 'lib/parse/model/classes/audience.rb', line 125

def cache_mutex
  @cache_mutex ||= Mutex.new
end

.cleanup_expired_cache!Integer

Remove expired entries from cache to prevent memory leaks Called automatically during cache_fetch, but can also be called manually

Returns:

  • (Integer)

    number of entries removed



117
118
119
120
121
# File 'lib/parse/model/classes/audience.rb', line 117

def cleanup_expired_cache!
  cache_mutex.synchronize do
    cleanup_expired_cache_entries
  end
end

.clear_cache!

This method returns an undefined value.

Clear the audience cache



76
77
78
79
80
81
# File 'lib/parse/model/classes/audience.rb', line 76

def clear_cache!
  cache_mutex.synchronize do
    @audience_cache = {}
    @cache_timestamps = {}
  end
end

.find_by_name(name, cache: true) ⇒ Parse::Audience?

Find an audience by name (uses cache by default).

Examples:

audience = Parse::Audience.find_by_name("VIP Users")
audience = Parse::Audience.find_by_name("VIP Users", cache: false)  # Bypass cache

Parameters:

  • name (String)

    the audience name

  • cache (Boolean) (defaults to: true)

    whether to use cache (default: true)

Returns:



233
234
235
# File 'lib/parse/model/classes/audience.rb', line 233

def find_by_name(name, cache: true)
  cache_fetch(name, cache: cache)
end

.installation_count(audience_name) ⇒ Integer

Get the count of installations matching an audience's query.

Examples:

count = Parse::Audience.installation_count("VIP Users")

Parameters:

  • audience_name (String)

    the audience name

Returns:

  • (Integer)

    the count of matching installations

Raises:

  • (Parse::Push::AudienceNotFound)

    if no audience exists with the given name. Previously returned 0 on miss, which was indistinguishable from "audience exists but matches nothing."



245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/parse/model/classes/audience.rb', line 245

def installation_count(audience_name)
  audience = find_by_name(audience_name)
  if audience.nil?
    raise Parse::Push::AudienceNotFound,
          "Audience '#{audience_name}' not found in _Audience collection"
  end
  return 0 unless audience.query.present?

  q = Parse::Installation.query
  audience.query.each do |key, value|
    q.where(key.to_sym => value)
  end
  q.count
end

.installations(audience_name) ⇒ Parse::Query

Get a query for installations matching an audience.

Examples:

installations = Parse::Audience.installations("VIP Users").all

Parameters:

  • audience_name (String)

    the audience name

Returns:

Raises:

  • (Parse::Push::AudienceNotFound)

    if no audience exists with the given name. Previously returned an unconstrained Installation query on miss, which silently elevated the result set from "matches this audience" to "every Installation" — the same fail-open footgun as Push#to_audience.



270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/parse/model/classes/audience.rb', line 270

def installations(audience_name)
  audience = find_by_name(audience_name)
  if audience.nil?
    raise Parse::Push::AudienceNotFound,
          "Audience '#{audience_name}' not found in _Audience collection"
  end
  q = Parse::Installation.query
  if audience.query.present?
    audience.query.each do |key, value|
      q.where(key.to_sym => value)
    end
  end
  q
end

Instance Method Details

#format_value(key, val, data_type = nil) ⇒ Object

JSON-encode a Hash/Array assigned to the query field before the :string property coercion runs. Every assignment path — query=, query_constraint=, mass-assignment via new(query:), and server hydration — funnels through format_value, so intercepting here (rather than the public setter, which mass-assignment bypasses) is the single reliable place to keep the wire value valid JSON ({"k":"v"}) instead of Ruby's Hash#to_s ({"k"=>"v"}). A String passed in (e.g. a row loaded from the server) falls through to the normal string coercion untouched.



180
181
182
183
184
185
# File 'lib/parse/model/classes/audience.rb', line 180

def format_value(key, val, data_type = nil)
  if key == :query_json && (val.is_a?(Hash) || val.is_a?(Array))
    return val.to_json
  end
  super
end

#installation_countInteger

Get the count of installations matching this audience's query.

Examples:

audience = Parse::Audience.first
puts "#{audience.name} has #{audience.installation_count} members"

Returns:

  • (Integer)

    the count of matching installations



291
292
293
294
295
296
297
298
299
# File 'lib/parse/model/classes/audience.rb', line 291

def installation_count
  return 0 unless query.present?

  q = Parse::Installation.query
  query.each do |key, value|
    q.where(key.to_sym => value)
  end
  q.count
end

#installationsParse::Query

Get a query for installations matching this audience.

Examples:

audience.installations.each { |i| puts i.device_token }

Returns:



305
306
307
308
309
310
311
312
313
# File 'lib/parse/model/classes/audience.rb', line 305

def installations
  q = Parse::Installation.query
  if query.present?
    query.each do |key, value|
      q.where(key.to_sym => value)
    end
  end
  q
end

#query_constraintHash

Alias for query to match Parse Server naming conventions.

Returns:

  • (Hash)

    The query constraint hash.



215
216
217
# File 'lib/parse/model/classes/audience.rb', line 215

def query_constraint
  query
end

#query_constraint=(constraints) ⇒ Object

Set the query constraint.

Parameters:

  • constraints (Hash)

    The query constraint hash.



221
222
223
# File 'lib/parse/model/classes/audience.rb', line 221

def query_constraint=(constraints)
  self.query = constraints
end