<commit_before>---
layout: disposable
title: "Disposable API"
---

# Twin API

**A twin is an intermediate object** that usually sits between persistence layer and your application code. It's a _domain object_ that helps you model your application domain. Sometimes this is called _decorator_.

Often, a twin maps directly to one persistent _"model"_. However, twins are absolutely not limited to your database layout - the opposite is the case. One twin can be a composition of many underlying models, with renaming, delegating, and more mapping features, that allow you modelling objects for your application, and not objects dictated by your database layout.

## Declarative API

Every twin is based on a schema which comes in form of a `Disposable::Twin` class.

	class AlbumTwin < Disposable::Twin
	  property :title

	  collection :songs do
	    property :name
	    property :index
	  end

	  property :artist do
	    property :full_name
	  end
	end

The self-explaining DSL known from many Trailblazer gems allows to define flat or nested twins.

## Unnest

To expose a nested property on an outer level, use `::unnest`.

    class AlbumTwin < Disposable::Twin
      property :artist do
        property :email
      end

      unnest :email, from: :artist
    end

The `email` accessors will now be on top-level, hiding the nested structure to the outside world.

    album = Album.find(1)
    twin  = AlbumTwin.new(album)

    twin.email #=> "<EMAIL>"
    twin.email = "<EMAIL>"

When `sync`ing, only the nested structure will be considered.

    twin.sync
    album.artist.email #=> "<EMAIL>"

## Public API

The public twin API is unbelievably simple.

1. `Twin::new` creates and populates the twin.
1. `Twin#"reader"` returns the value or nested twin of the property.
1. `Twin#"writer"=(v)` writes the value to the twin, not the model.
1. `Twin#sync` writes all values to the model.
1. `Twin#save` writes all values to the model and calls `save` on configured models.


## Constructor

Twins get populated from the decorated models.

```ruby
Song   = Struct.new(:name, :index)
Artist = Struct.new(:full_name)
Album  = Struct.new(:title, :songs, :artist)
```

You need to pass model and the facultative options to the twin constructor.

```ruby
album = Album.new("Nice Try")
twin  = AlbumTwin.new(album, playable?: current_user.can?(:play))
```

## Readers

This will create a composition object of the actual model and the hash.

```ruby
twin.title     #=> "Nice Try"
twin.playable? #=> true
```

You can also override `property` values in the constructor:

```ruby
twin = AlbumTwin.new(album, title: "Plasticash")
twin.title #=> "Plasticash"
```

## Writers

Writers change values on the twin and are _not_ propagated to the model.

```ruby
twin.title = "Skamobile"
twin.title  #=> "Skamobile"
album.title #=> "Nice Try"
```

Writers on nested twins will "twin" the value.

```ruby
twin.songs #=> []
twin.songs << Song.new("Adondo", 1)
twin.songs  #=> [<Twin::Song name="Adondo" index=1 model=<Song ..>>]
album.songs #=> []
```

The added twin is _not_ passed to the model. Note that the nested song is a twin, not the model itself.

## Sync

Given the above state change on the twin, here is what happens after calling `#sync`.

```ruby
album.title  #=> "Nice Try"
album.songs #=> []

twin.sync

album.title  #=> "Skamobile"
album.songs #=> [<Song name="Adondo" index=1>]
```

`#sync` writes all configured attributes back to the models using public setters as `album.name=` or `album.songs=`. This is recursive and will sync the entire object graph.

Note that `sync` might already trigger saving the model as persistence layers like ActiveRecord can't deal with `collection= []` and instantly persist that.

You may implement your syncing manually by passing a block to `sync`.

```ruby
twin.sync do |hash|
  hash #=> {
  #  "title"     => "Skamobile",
  #  "playable?" => true,
  #  "songs"     => [{"name"=>"Adondo"...}..]
  # }
end
```

Invoking `sync` with block will _not_ write anything to the models.

Needs to be included explicitly (`Sync`).

## Save

Calling `#save` will do `sync` plus calling `save` on all nested models. This implies that the models need to implement `#save`.

```ruby
twin.save
#=> album.save
#=>      .songs[0].save

```

Needs to be included explicitly (`Save`).
<commit_msg>update twin API docs.<commit_after>---
layout: disposable
title: "Disposable API"
---

# Twin API

**A twin is an intermediate object** that usually sits between persistence layer and your application code. It's a _domain object_ that helps you model your application domain. Sometimes this is called _decorator_.

Often, a twin maps directly to one persistent _"model"_. However, twins are absolutely not limited to your database layout - the opposite is the case. One twin can be a composition of many underlying models, with renaming, delegating, and more mapping features, that allow you modelling objects for your application, and not objects dictated by your database layout.

## Declarative API

Every twin is based on a schema which comes in form of a `Disposable::Twin` class.

	class AlbumTwin < Disposable::Twin
	  property :title

	  collection :songs do
	    property :name
	    property :index
	  end

	  property :artist do
	    property :full_name
	  end
	end

The self-explaining DSL known from many Trailblazer gems allows to define flat or nested twins.

## Unnest

To expose a nested property on an outer level, use `::unnest`.

    class AlbumTwin < Disposable::Twin
      property :artist do
        property :email
      end

      unnest :email, from: :artist
    end

The `email` accessors will now be on top-level, hiding the nested structure to the outside world.

    album = Album.find(1)
    twin  = AlbumTwin.new(album)

    twin.email #=> "<EMAIL>"
    twin.email = "<EMAIL>"

When `sync`ing, only the nested structure will be considered.

    twin.sync
    album.artist.email #=> "<EMAIL>"

## Public API

The public twin API is unbelievably simple.

1. `Twin::new` creates and populates the twin.
1. `Twin#"reader"` returns the value or nested twin of the property.
1. `Twin#"writer"=(v)` writes the value to the twin, not the model.
1. `Twin#sync` writes all values to the model.
1. `Twin#save` writes all values to the model and calls `save` on configured models.


## Constructor

Twins get populated from the decorated models.

```ruby
Song   = Struct.new(:name, :index)
Artist = Struct.new(:full_name)
Album  = Struct.new(:title, :songs, :artist)
```

You need to pass model and the facultative options to the twin constructor.

```ruby
album = Album.new("Nice Try")
twin  = AlbumTwin.new(album, playable?: current_user.can?(:play))
```

## Readers

This will create a composition object of the actual model and the hash.

```ruby
twin.title     #=> "Nice Try"
twin.playable? #=> true
```

You can also override `property` values in the constructor:

```ruby
twin = AlbumTwin.new(album, title: "Plasticash")
twin.title #=> "Plasticash"
```

## Writers

Writers change values on the twin and are _not_ propagated to the model.

```ruby
twin.title = "Skamobile"
twin.title  #=> "Skamobile"
album.title #=> "Nice Try"
```

Writers on nested twins will "twin" the value.

```ruby
twin.songs #=> []
twin.songs << Song.new("Adondo", 1)
twin.songs  #=> [<Twin::Song name="Adondo" index=1 model=<Song ..>>]
album.songs #=> []
```

The added twin is _not_ passed to the model. Note that the nested song is a twin, not the model itself.

## Sync

To write twin data back to the models, use `sync`. You have to include `Sync`.

    class AlbumTwin < Disposable::Twin
      feature Sync
      #..
    end

Given the above state change on the twin, here is what happens when calling `#sync`.

```ruby
album.title  #=> "Nice Try"
album.songs #=> []

twin.sync

album.title  #=> "Skamobile"
album.songs #=> [<Song name="Adondo" index=1>]
```

`#sync` writes all configured attributes back to the models using public setters such as `album.name=` or `album.songs=`. This works recursively and will sync the entire object graph.

<div class="callout warning">
  <p>
    Note that <code>sync</code> might unexpectedly trigger saving of the model. Some persistence layers, for instance ActiveRecord, can't deal with <code>collection= []</code> and instantly persist that.
  </p>
</div>

### Sync with Block

You may implement your syncing manually by passing a block to `sync`. The block argument is a nested hash of property/value pairs.

```ruby
twin.sync do |hash|
  hash #=> {
  #  "title"     => "Skamobile",
  #  "playable?" => true,
  #  "songs"     => [{ "name"=>"Adondo", ..}],
  #  "composer"  => nil
  # }
end
```

Invoking `sync` with block will _not_ write anything to the models. Note that `nil` values are included into the hash, too (0.4.0).

## Save

Calling `#save` will do `sync` plus calling `save` on all nested models. This implies that the models need to implement `#save`.

```ruby
twin.save
#=> album.save
#=>      .songs[0].save

```

Needs to be included explicitly (`Save`).

{'fraction_non_alphanumeric': 0.11340775947828009, 'fraction_numerical': 0.002542279208577429, 'mean_word_length': 3.767123287671233, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '199436', 'n_tokens_mistral': 2927, 'n_tokens_neox': 2755, 'n_words': 1168}