Object Specific Sync Rules

I am trying to set up v2 sync rules based on the relationships defined in my data model. For this example there are 3 pertinent entities:

  • Channel
  • Product
  • Product Monitor

A user has a simple belongs-to relationship to a channel, and a channel has 2 "has many" relationships to products and product monitors. Here is a trimmed-down version of the data model:

<data-model>
    <model name="user" label="User">
        <field name="name" label="Name" type="text:name"/>

        <belongs-to model="channel" name="Channel"/>

        <display>{name}</display>
    </model>


    <model name="channel" label="Channel">
        <field name="tsf_code" label="TSF Code" type="text:name"/>
        <field name="name" label="Name" type="text:name"/>

        <has-many model="channel_product" name="Sells" />
        <has-many model="product_monitor" name="Monitors" />
        <display>{name}</display>
    </model>

    <!-- Link to enable many to many relationship between channels and products -->
    <model name="channel_product" label="ChannelProduct">
        <belongs-to model="channel"/>
        <belongs-to model="product"/>
        <display>{channel}{product}</display>
    </model>

    <model name="product" label="Product">
        <field name="name" label="Name" type="text:name"/>
        <field name="code" label="Code" type="text"/>

        <belongs-to model="channel_product" name="Soldby"/>

        <display>{name}</display>
    </model>

    <model name="product_monitor" label="Product Monitor">
        <field name="kind" label="Kind" type="single-choice">
            <option key="mtd">Month To Date</option>
            <option key="ytd">Year To Date</option>
            <option key="tdy">Today</option>
            <option key="all">All time</option>
        </field>        

        <belongs-to model="channel" name="Channel"/>
        <belongs-to model="product" name="Product"/>

        <display>{Channel}:{Product} ({kind})</display>
    </model>

</data-model>

My aim is to set up sync rules to only push down the product and product monitor objects linked to the user via the channel - which seems consistent with the example at https://docs.journeyapps.com/reference/container/introduction-to-sync-rules/sync-rules-v2

The only difference from the example I see is the additional link to channel_product, so I am not 100% sure how to incorporate this?

Finally, here is the sync rule I defined for the above:

 <bucket via="self/Channel">
        <!-- The root itself (channel in this case) is automatically included in the bucket -->

        <!-- sync all products linked to this channel via the has-many relationship -->
        <has-many name="Sells" />

        <!-- sync all product monitors linked to this channel via the has-many relationship -->
        <has-many name="Monitors" />
  </bucket>    

Assuming I have set up my data corrcetly, I would expect to get the approriate objects whenever I do a DB.model.all()?

Here is a little test method to do that:

function fetchMonitors() {
    var monitors = 
        DB.product_monitor.all().toArray();
    console.log('Monitors:',monitors);
    var product = 
        DB.product.all().toArray();
    console.log('Products:',product);
    var channel = 
        DB.channel.all().toArray();
    console.log('Channels:',channel);

    notification.success('Counts: Channel[' + channel.length +'] Product ['+product.length+'] Monitor ['+monitors.length+']');
}

At the moment the only object that does return is the Channel (so the root of my sync rule).

Am I missing something? Should I set up additional sync bucket for the many-to-many link "channel_product"?

For synching object specific buckets, everything you want to sync within the bucket needs a direct belongs-to relationship to the root of the bucket and the the has-many side of those relationships need to be explicitly listed on the root object model. This allows you to list each of the related models that you want to sync inside of the object specific bucket using the has-many syntax.

In your case, with channel as the root of the bucket, your Sells and Monitors relationships link to the channel_product and product_monitor models respectively, but not the product model.

So, if you want to keep your join table as is, then simply add the product model to a global sync bucket.

In essence then you would sync all products, but in the app you would use the join table entries, which are synced off the user's Channel relationship, to limit the actual product objects that the user can interact with.

2 notes though

  1. In your test, if your product_monitor array is empty it means the relationship to channel on the product_monitor objects are not set correctly, else it would sync using the Monitors relationship specified.
  2. I don't quite understand your data structure, for example I don't understand why you have the following relationship defined on your product model
<belongs-to model="channel_product" name="Soldby"/>
1 Like
  1. I also expected product_monitor to work since there is no difference to the example in the documentation. I will test a scenario with only that relationship again. 2. The channel_product relationship is added to facilitate the many-to-many relationship between channels and products.

Just a follow-up - I see your point on the relationship - the channel seems redundant so I could achieve the same thing by just adding a relationship back to Channel - will play around with this, thank you for the pointer.

More clarity on the channel_product relationship: A product can be sold by many channels, so I cannot just have a belongs-to Channel on the product model, since that would mean a product can only be sold by 1 channel - hence the “link” model channel_product.

@HeinzSeldte You have an implicit has-many channel_products relationship on your product model, this facilitates the many-to-many relationship between channel and products through your join table. My contention is that there is no need for a product to belong to a single channel_product, that is the relationship I don’t understand, the so-called ‘SoldBy’ relationship. Unless that is the relationship you meant to use to indicate which channels sell any specific product, in which case you need to define it not as a belongs-to, but rather a has-many.

After some refactoring I got it working and thought I would post updated info in case someone else has a similar scenario.

Updated data model (renamed the confusing channel_product relationship to a more descriptive "Sales Agreement" model)

<model name="user" label="User">
    <belongs-to model="channel" name="Channel"/>
    <has-many model="lead" name="Leads"/>
    <display>{name}</display>
</model>

<model name="channel" label="Channel">
    <field name="name" label="Name" type="text:name"/>
    <has-many model="sales_agreement" name="Salesagreements" />
    <has-many model="product_monitor" name="Monitors" />
    <display>{name}</display>
</model>

<model name="product" label="Product">
    <field name="name" label="Name" type="text:name"/>
    <field name="code" label="Code" type="text"/>
    <display>{name}</display>
</model>

<model name="sales_agreement" label="Sales Agreement">
    <field name="kind" label="Kind" type="single-choice">
        <option key="default">Default</option>
        <option key="binder">Binder</option>
        <option key="custom">Custom</option>
    </field>        
    <field name="start_date" label="Start date" type="date"/>
    <field name="open_ended" label="Open ended" type="boolean"/>
    <field name="end_date" label="End date" type="date"/>
    <field name="active" label="Active" type="boolean"/>
    <belongs-to model="channel" name="Channel"/>
    <belongs-to model="product" name="Product"/>
    <display>{Channel}:{Product} ({kind})</display>
</model>

<model name="product_monitor" label="Product Monitor">
    <field name="kind" label="Kind" type="single-choice">
        <option key="mtd">Month To Date</option>
        <option key="ytd">Year To Date</option>
        <option key="tdy">Today</option>
        <option key="all">All time</option>
    </field>        
    <belongs-to model="channel" name="Channel"/>
    <belongs-to model="product" name="Product"/>
    <display>{Channel}:{Product} ({kind})</display>
</model>

And then - thanx to Tielman's suggestion of also sending down Products I moved it from the global bucket to a specific bucket for the product (via an active sales agreement). So the sync rules now look like this (with an empty global bucket):

<!-- Ensure we send only the users own model -->
<bucket via="self">
</bucket>

<!-- Send agreements and monitors linked to channel -->
<bucket via="self/Channel">
        <has-many name="Salesagreements" />
        <has-many name="Monitors" />
</bucket>    

<!-- Ensure we send only products linked to an active sales agreement -->
<bucket via="self/Channel/Salesagreements[active == true]/Product">
</bucket>    
2 Likes