class Document
attr_reader :state, :current_user
def initialize
@state = DraftState.new(self)
@current_user = User.new('admin')
end
def set_state(new_state)
@state = new_state
end
def publish
@state.publish
end
def unpublish
@state.unpublish
end
User = Struct.new(:role)
end
class State
attr_reader :context
def initialize(context)
@context = context
end
def publish
raise NotImplementedError
end
def unpublish
raise NotImplementedError
end
end
class DraftState < State
def publish
next_state = ReviewState.new(context)
context.set_state(next_state)
next_state.to_s
end
def to_s
'draft'
end
end
class ReviewState < State
def publish
if context.current_user.role == 'admin'
next_state = PublishedState.new(context)
context.set_state(next_state)
next_state.to_s
else
"Admin must review."
end
end
def unpublish
next_state = DraftState.new(context)
context.set_state(next_state)
next_state.to_s
end
def to_s
'under review'
end
end
class PublishedState < State
def unpublish
next_state = DraftState.new(context)
context.set_state(next_state)
next_state.to_s
end
def to_s
'published'
end
end
# State Pattern Example in Ruby
> The State pattern suggests to create new classes for all possible states of a context object and to extract the state-related behaviors into these classes.
The context will contain a reference to a state object that represents its current state. Instead of performing a behavior on its own, the context will delegate the execution to a state object.
To change the context's state, one would pass another state object to the context. But to make states interchangeable, all states classes must follow the common interface, and the context must communicate with its state object via that interface.
The described structure may look like the Strategy pattern, but there is one key difference. In the State pattern, the context, as wells as particular states, can initiate the transitions from one state to another.
Read more about the State Pattern here: https://refactoring.guru/design-patterns/state
## Example Usage
```
$ ⇒ pry
[1] pry(main)> require './state_pattern.rb'
=> true
[2] pry(main)> document = Document.new
=> #<Document:0x007f98b6a44470 @current_user=#<struct Document::User role="admin"> ...>>>
[3] pry(main)> document.publish
=> "under review"
[4] pry(main)> document.unpublish
=> "draft"
[5] pry(main)> document.publish
=> "under review"
[6] pry(main)> document.publish
=> "published"
[7] pry(main)> document.publish
NotImplementedError: NotImplementedError
[8] pry(main)> document.unpublish
=> "draft"
```
If the `context.current_user.role != "admin"`
```
[6] pry(main)> document.publish
=> "Admin must review."
[7] pry(main)> document.unpublish
=> "draft"
[8] pry(main)> document.publish
=> "under review"
[9] pry(main)> document.publish
=> "Admin must review."
```