Module: Parse::Associations::HasMany
- Included in:
- Object
- Defined in:
- lib/parse/model/associations/has_many.rb
Overview
Parse has many ways to implement one-to-many and many-to-many
associations: Array, Parse Relation or through a Query. How you decide
to implement your associations, will affect how has_many works in
Parse-Stack. Parse natively supports one-to-many and many-to-many
relationships using Array and Relations, as described in
Parse Relational Data.
Both of these methods require you define a specific column type in your
Parse table that will be used to store information about the association.
In addition to Array and Relation, Parse-Stack also implements the
standard has_many behavior prevalent in other frameworks through a query
where the associated class contains a foreign pointer to the local class,
usually the inverse of a belongs_to. This requires that the associated
class has a defined column that contains a pointer the refers to the
defining class.
Query-Approach
In this Query implementation, a has_many association for a Parse class
requires that another Parse class will have a foreign pointer that refers
to instances of this class. This is the standard way that has_many
relationships work in most databases systems. This is usually the case when
you have a class that has a belongs_to relationship to instances of the
local class.
In the example below, many songs belong to a specific artist. We set this
association by setting :belongs_to relationship from Song to Artist.
Knowing there is a column in Song that points to instances of an Artist,
we can setup a has_many association to Song instances in the Artist
class. Doing so will generate a helper query method on the Artist instance
objects.
class Song < Parse::Object property :released, :date
this class will have a pointer column to an Artist
belongs_to :artist end
class Artist < Parse::Object has_many :songs end
artist = Artist.first
artist.songs # => [all songs belonging to artist]
equivalent: Song.all(artist: artist)
filter also by release date
artist.songs(:released.after => 1.year.ago)
equivalent: Song.all(artist: artist, :released.after => 1.year.ago)
In order to modify the associated objects (ex. songs), you must modify
their corresponding belongs_to field (in this case song.artist), to
another record and save it.
Options for has_many using the Query approach are :as and :field. The
:as option behaves similarly to the :belongs_to counterpart. The
:field option can be used to override the derived column name located
in the foreign class. The default value for :field is the columnized
version of the Parse subclass parse_class method.
class Parse::User
since the foreign column name is :agent
has_many :artists, field: :agent end
class Artist < Parse::Object belongs_to :manager, as: :user, field: :agent end
artist.manager # => Parse::User object
user.artists # => [artists where :agent column is user]
When using this approach, you may also employ the use of scopes to filter the particular data from the has_many association.
class Artist has_many :songs, ->(timeframe) { where(:created_at.after => timeframe) } end
artist.songs(6.months.ago)
=> [artist's songs created in the last 6 months]
You may also call property methods in your scopes related to the instance. Note: You also have access to the instance object for the local class through a special "i" method in the scope.
class Concert property :city belongs_to :artist end
class Artist property :hometown has_many :local_concerts, -> { where(:city => hometown) }, as: :concerts end
assume
artist.hometown = "San Diego"
artist's concerts in their hometown of 'San Diego'
artist.local_concerts
equivalent: Concert.all(artist: artist, city: artist.hometown)
You may also omit the association completely, as rely on the scope to fetch the
associated records. This makes the has_many work as a macro query setting the :scope_only
option to true:
class Author < Parse::Object property :name has_many :posts, ->{ where :tags.in => name.downcase }, scope_only: true end
class Post < Parse::Object property :tags, :array end
author.posts # => Posts where author's name is a tag
equivalent: Post.all( :tags.in => artist.name.downcase )
Array-Approach
In the Array implemenatation, you can designate a column to be of Array
type that contains a list of Parse pointers. Parse-Stack supports this by
passing the option through: :array to the has_many method. If you use
this approach, it is recommended that this is used for associations where
the quantity is less than 100 in order to maintain query and fetch
performance. You would be in charge of maintaining the array with the proper
list of Parse pointers that are associated to the object. Parse-Stack does
help by wrapping the array in a PointerCollectionProxy which provides dirty tracking.
class Artist < Parse::Object end
class Band < Parse::Object has_many :artists, through: :array end
artist = Artist.first
find all bands that contain this artist
bands = Band.all( :artists.in => [artist.pointer] )
band = bands.first band.artists # => [array of Artist pointers]
remove artists
band.artists.remove artist
add artist
band.artists.add artist
save changes
band.save
ParseRelation-Approach
Other than the use of arrays, Parse supports native one-to-many and many-to-many
associations through what is referred to as a Parse Relation.
This is implemented by defining a column to be of type Relation which
refers to a foreign class. Parse-Stack supports this by passing the
through: :relation option to the has_many method. Designating a column
as a Parse relation to another class type, will create a one-way intermediate
"join-list" between the local class and the foreign class. One important
distinction of this compared to other types of data stores (ex. PostgresSQL) is that:
1. The inverse relationship association is not available automatically. Therefore, having a column of artists in a Band class that relates to members of the band (as Artist class), does not automatically make a set of Band records available to Artist records for which they have been related. If you need to maintain both the inverse relationship between a foreign class to its associations, you will need to manually manage that by adding two Parse relation columns in each class, or by creating a separate class (ex. ArtistBands) that is used as a join table.
2. Querying the relation is actually performed against the implicit join table, not the local one.
3. Applying query constraints for a set of records within a relation is performed against the foreign table class, not the class having the relational column.
The Parse documentation provides more details on associations, see Parse Relations Guide. Parse-Stack will handle the work for (2) and (3) automatically.
In the example below, a Band can have thousands of Fans. We setup a
Relation<Fan> column in the Band class that references the Fan class.
Parse-Stack provides methods to manage the relationship under the RelationCollectionProxy
class.
class Fan < Parse::Object
.. lots of properties ...
property :location, :geopoint end
class Band < Parse::Object has_many :fans, through: :relation end
band = Band.first
# the number of fans in the relation
band.fans.count
get the first object in relation
fan = bands.fans.first # => Parse::User object
use add or remove to modify relations
band.fans.add user bands.fans.remove user
updates the relation as well as changes to band
band.fans.save
Find 50 fans who are near San Diego, CA
downtown = Parse::GeoPoint.new(32.82, -117.23) fans = band.fans.all :location.near => downtown
You can perform atomic additions and removals of objects from has_many
relations using the methods below. Parse allows this by providing a specific atomic operation
request. The operation is performed directly on Parse server
and NOT on your instance object.
atomically add/remove
band.artists.add! objects # { __op: :AddUnique } band.artists.remove! objects # { __op: :AddUnique }
atomically add unique Artist
band.artists.add_unique! objects # { __op: :AddUnique }
atomically add/remove relations
band.fans.add! users # { __op: :Add } band.fans.remove! users # { __op: :Remove }
atomically perform a delete operation on this field name
this should set it as undefined.
band.op_destroy!("category") # { __op: :Delete }
You can also perform queries against class entities to find related objects. Assume
that users can like a band. The Band class can have a likes column that is
a Parse relation to the User class containing the users who have liked a
specific band.
class Band < Parse::Object
# likes is a Parse relation column of user objects.
has_many :likes, through: :relation, as: :user
end
You can now find all User records who have liked a specific band. In the
example below, the :likes key refers to the likes column defined in the Band
collection which contains the set of user records.
band = Band.first # get a band
# find all users who have liked this band, where :likes is a column
# in the Band collection - NOT in the User collection.
users = Parse::User.all :likes. => band
You can also find all bands that a specific user has liked.
user = Parse::User.first
# find all bands where this user
# is in the `likes` column of the Band collection
bands_liked_by_user = Band.all :likes => user
Class Attribute Summary collapse
-
.relations ⇒ Hash
A hash mapping of all has_many associations that use the ParseRelation implementation.
Class Method Summary collapse
-
.has_many(key, scope = nil, opts = {}) ⇒ Array<Parse::Object>, ...
Define a one-to-many or many-to-many association between the local model and a foreign class.
Instance Method Summary collapse
-
#relation_changes? ⇒ Boolean
True if there are pending relational changes for.
-
#relation_updates ⇒ Hash
A hash of all the relation changes that have been performed on this instance.
-
#relations ⇒ Hash
A hash list of all has_many associations that use a Parse Relation.
Class Attribute Details
.relations ⇒ Hash
A hash mapping of all has_many associations that use the ParseRelation implementation.
|
|
# File 'lib/parse/model/associations/has_many.rb', line 272
|
Class Method Details
.has_many(key, scope = nil, opts = {}) ⇒ Array<Parse::Object>, ...
Define a one-to-many or many-to-many association between the local model and a foreign class.
Options for has_many are the same as the BelongsTo.belongs_to counterpart with
support for :required, :as and :field. It has additional options.
|
|
# File 'lib/parse/model/associations/has_many.rb', line 276
|
Instance Method Details
#relation_changes? ⇒ Boolean
Returns true if there are pending relational changes for.
623 624 625 |
# File 'lib/parse/model/associations/has_many.rb', line 623 def relation_changes? changed.any? { |key| relations[key.to_sym] } end |
#relation_updates ⇒ Hash
A hash of all the relation changes that have been performed on this instance. This is only used when the association uses Parse Relations.
612 613 614 615 616 617 618 619 620 |
# File 'lib/parse/model/associations/has_many.rb', line 612 def relation_updates h = {} changed.each do |key| next unless relations[key.to_sym].present? && send(key).changed? remote_field = self.field_map[key.to_sym] || key h[remote_field] = send key # we still need to send a proxy collection end h end |
#relations ⇒ Hash
A hash list of all has_many associations that use a Parse Relation.
605 606 607 |
# File 'lib/parse/model/associations/has_many.rb', line 605 def relations self.class.relations end |