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
.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.
574 575 576 | # File 'lib/parse/model/associations/has_many.rb', line 574 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.
563 564 565 566 567 568 569 570 571 | # File 'lib/parse/model/associations/has_many.rb', line 563 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.
556 557 558 | # File 'lib/parse/model/associations/has_many.rb', line 556 def relations self.class.relations end |