# Inter

Easily add arbitrary attributes to an ActiveRecord model on the fly, without the need of any extra new database columns or migrations.

## Why?

Ever added a 5th boolean to a model just so you could keep track if _something_ had happened to this user, article, etc? With __Inter__ you can add any arbitrary attributes to any ActiveRecord model on the fly without the need to add yet another database migration. Instead all __interactions__ are stored in a seperate table and hooked up using polymorphism. Using a simple DSL you can then quickly manipulate and query interactions.

Benefits:

* Cleaner models
* Simple and powerful DSL
* More lightweight than a full state machine
* Can store any value

## Usage

## Dependencies

* >= Ruby 2.0

## Installation

Add the gem to your Gemfile.

```ruby
gem "inter"
```

Then generate and run the database migrations.

```shell
rails generate inter:install
rake db:migrate
```

And finally define an ActiveRecord model as an interactable.

```ruby
class Article < ActiveRecord::Base
  include Inter::Actable
end
```

## Basic usage

### Instance methods

Setting a value on an Article is now easy. Just pick a key name and set it to a desired value.

```ruby
# simple boolean setting and getting2
article.is_published! # sets the published interaction to true
article.isnt_published! # sets the published interaction to false
article.is_published? #=> false

# you can also combine keys
article.is_published_and_not_promoted! #=> nil
article.is_published_and_promoted? #=> false
article.is_published_and_not_promoted? #=> true

# more complex values
article.set :tags, [:ruby, :rails, :gem] # or article.set "tags", [:ruby, :rails, :gem]
article.get :tags #=> ["ruby", "rails", "gem"]
article.has? :tags #=> true
article.set :tags, nil
article.has? :tags #=> false
```

### Class methods

You can query interactable objects in a similar fasion.

```ruby
# similar to instance methods there are simple lookup methods
Article.is_published #=> all articles for which published == true
Article.isnt_published #=> all articles for which published != true

# again you can combine interactions
Article.is_published_and_promoted #=> all promoted and published articles
```

## Interaction history

One of the advantages of Interactions is that they are kept as a permanent log. Thefefore you can easily look up if an interaction has ever happened in the past.

```ruby
# look up if an article has ever been published
article.was_published? #=> false
article.is_published! #=> nil
article.isnt_published! #=> nil
article.is_published? #=> false
article.was_published? #=> true
article.wasnt_published? #=> false

# again you can combine keys
article.was_published_and_promoted? #=> false

# to fully inspect the log you need to dig a bit deeper
# and access the interactions directly
article.history :published #=> all interactions that match this key
article.history "published"
article.history :published, :promoted
article.history ["published", "promoted"]
```

## Advanced usage

A lot of the magic methods have underlying dumb methods that can be used too to further customize your needs.

### Instance methods

```ruby
article.is "published" #=> nil
article.is :published, :promoted #=> nil
article.is [:published, :not_promoted] #=> nil
article.is? :published, :promoted #=> false
article.is? [:published, :not_promoted] #=> true
article.get :promoted #=> true
```

### Class methods

```ruby
Article.inter(published: true) # loads all articles for which the current published state us true
Article.inter(published: true).first # lazy loads the latest article
# You can also get access to the raw interactions
Article.interactions(published: true)
```

### Related lookup methods

Finally if 2 related models are both interactable then you can query related models by interaction status.

```ruby
class Article < ActiveRecord::Base
  include Inter::Actable
  has_many :comments
end

class Comment < ActiveRecord::Base
  include Inter::Actable
  belongs_to :article
end
```

```ruby
article.comments.first.is_spam! #=> nil
article.spam_comments #=> all comments with an interaction of "spam" set to true
article.not_spam_comments #=> all comments with an interaction of "spam" != true
article.spam_comments_ids #=> an array of IDs
article.spam_comments_count #=> the length of all the comments that match the criteria

# all of this maps down to the known pattern
article.comments.inter(spam: true)
```

## Changelog

* 0.0.2 Copied over implementation
* 0.0.1 Gem skeleton

## Contributors

* [Cristiano Betta](http://github.com/cbetta) - Developer Evangelist - PayPal

## Todos

* Add a generator
* Add tests
* Add option to disable history

## License

See [LICENSE](/LICENSE)