Skip to content

Features and Recipes

Kareem Kouddous edited this page Jun 17, 2014 · 4 revisions

Attributes

Since we are doing database replication, it's important to distinguish that Promiscuous operates at the ActiveModel API, and thus makes no difference between the model methods and the actual persisted attributes. We call virtual attributes computed attributes that are published. For example:

class User
  include Promiscuous::Publisher
  field :first_name, :last_name
  publish :full_name, :use => [:first_name, :last_name]

  def full_name
    "#{first_name} #{last_name}"
  end
end

Any attributes that respond to .to_json can be published, including array and hashes. We do not recommend using deeply nested structures without using embedded documents though.

On the subscriber side, Promiscuous invokes the setter corresponding to the subscribed attribute.

Note that you must declare which fields are dependent by passing in the list of dependent fields using the :use option as show above. If you do not do this then changes will not be published if the virtual attribute changes.

Ephemerals & Observers

In some situations, publishing data models that are not persisted to the database can be very useful:

# Publisher side
class UserEvent
  include Promiscuous::Publisher::Model::Ephemeral
  attr_accessor :user_id, :event_name
  publish :user_id, :event_name
end

UserEvent.create(:user_id => 123, :event_name => :registered)

# Subscriber side
class UserEvent
  include Promiscuous::Subscriber::Model::Observer
  attr_accessor :user_id, :event_name
  subscribe :user_id, :event_name

  after_create do
    Mailer.send_email(:member_id => user_id, :type => :sign_up)
  end
end

Promiscuous allows you to mix persisted models with ephemeral and observers. For example, one would use a mailer application that observes the user state attribute to send the appropriate email.

Polymorphism

When publishing a model, the class hierarchy is also published to allow the subscriber to map classes. For example:

# Publisher side
class User
  include Promiscuous::Publisher
  publish :name, :email
end

class Member < User
  publish :points
end

class Admin < User
  publish :role
end

# Subscriber side
class User
  include Promiscuous::Subscriber
  subscribe :name, :email
end

class Member < User
  subscribe :points
end

Notice that the Admin model is not subscribed. When the subscriber receives an admin model, the subscriber looks for its nearest parent, which is the User model, and treat the received admin as a user. Promiscuous follows polymorphism rules by traversing the published inheritance chain to find the subscriber's subclass. This can be quite handy to collapse a tree of subclasses at a given node in the hierarchy tree.

Embedded Documents

Promiscuous supports embedded documents (mongoid only feature):

class Address
  include Mongoid::Document
  include Promiscuous::Publisher

  publish :street_name, :zipcode
end

class User
  include Mongoid::Document
  include Promiscuous::Publisher
  embeds_many :addresses

  publish :name, :email, :addresses
end

Foreign Keys

When mixing databases in the system, the primary key formats may not be compatible. Using a foreign key with a different column can be handy in this case. Example:

# Publisher side
class User
  include Mongoid::Document
  include Promiscuous::Publisher
  publish :name, :email
end

# Subscriber side
class User < ActiveRecord::Base
  include Promiscuous::Subscriber
  subscribe :name, :email, :foreign_key => :crowdtap_id
end

Namespace Mapping

Overriding class names

By default, Promiscuous assumes that the model names are the same on the publisher and subscriber but this can be overridden with the :as argument.

# Publisher side
class User
  include Promiscuous::Publisher
  publish :name, :email, :as => :Member
end

# Subscriber side
class CrowdtapMember
  include Promiscuous::Subscriber
  subscribe :name, :email, :as => :Member
end

Polymorphic class names

When using polymorphism, Promiscuous will attempt to map an object to the lowest possible class in its descendant chain. This opens up the possibility to collapse a hierarchy in a subscriber if desired:

# Publisher side
class User
  include Promiscuous::Publisher
  publish :name, :email
end

class Member < User
  publish :points
end

class Admin < User
  publish :role
end

# Subscriber side
class User
  include Promiscuous::Subscriber
  subscribe :name, :email, :role, :points
end

An Admin instance on the publisher will be subscribed as a User on the subscriber and still map the points attribute correctly.

Promiscuous DSL

There are two ways to define the publishers and subscribers attributes:

  1. directly in the model as shown in the examples above 2) in a separate file. In big applications, code organization is crucial. Depending on your philosophy, you can pick one way or the other.

In the model

class User
  include Promiscuous::Publisher
  publish :name
  publish :email
end

# Or more succinctly:
class User
  include Promiscuous::Publisher
  publish :name, :email
end

In the model with Mongoid

With Mongoid, you can wrap all the field declarations in a publish/subscribe block. We recommand this syntax for subscribers as it serve as documentation.

class User
  include Mongoid::Document
  include Promiscuous::Publisher

  publish do
    field :name
    field :email
    belongs_to :group
  end

  field :password
end

Promiscuous definitions are automatically reloaded at every requests in development mode on the publisher side (TODO: reload for subscribers as well).

In a config file

In some ways, the configuration file is the analogous of the ./config/routes.rb file when comparing promiscuous and rails controllers.

Promiscuous will load any of the following files to find your Promiscuous definitions:

  • ./config/promiscuous.rb
  • ./config/publishers.rb
  • ./config/subscribers.rb

Use Promiscuous.define as shown below. Note that you must specify the published/subscribed model name pluralized:

Promiscuous.define do
  publish :user do
    attributes :name, :email
  end
end