README.md in cistern-0.10.2 vs README.md in cistern-0.11.0

- old
+ new

@@ -13,284 +13,342 @@ #### Requests Requests are enumerated using the `request` method and required immediately via the relative path specified via `request_path`. - class Foo::Client < Cistern::Service - request_path "my-foo/requests" +```ruby +class Foo::Client < Cistern::Service + request_path "my-foo/requests" - request :get_bar # require my-foo/requests/get_bar.rb - request :get_bars # require my-foo/requests/get_bars.rb + request :get_bar # require my-foo/requests/get_bar.rb + request :get_bars # require my-foo/requests/get_bars.rb - class Real - def request(url) - Net::HTTP.get(url) - end - end + class Real + def request(url) + Net::HTTP.get(url) end + end +end +``` <!--todo move to a request section--> A request is method defined within the context of service and mode (Real or Mock). Defining requests within the service mock class is optional. - # my-foo/requests/get_bar.rb - class Foo::Client - class Real - def get_bar(bar_id) - request("http://example.org/bar/#{bar_id}") - end - end # Real +```ruby +# my-foo/requests/get_bar.rb +class Foo::Client + class Real + def get_bar(bar_id) + request("http://example.org/bar/#{bar_id}") + end + end # Real - # optional, but encouraged - class Mock - def get_bars - # do some mock things - end - end # Mock - end # Foo::client + # optional, but encouraged + class Mock + def get_bars + # do some mock things + end + end # Mock +end # Foo::client +``` All declared requests can be listed via `Cistern::Service#requests`. - Foo::Client.requests # => [:get_bar, :get_bars] +```ruby +Foo::Client.requests # => [:get_bar, :get_bars] +``` #### Models and Collections Models and collections have declaration semantics similar to requests. Models and collections are enumerated via `model` and `collection` respectively. - class Foo::Client < Cistern::Service - model_path "my-foo/models" +```ruby +class Foo::Client < Cistern::Service + model_path "my-foo/models" - model :bar # require my-foo/models/bar.rb - collection :bars # require my-foo/models/bars.rb - end + model :bar # require my-foo/models/bar.rb + collection :bars # require my-foo/models/bars.rb +end +``` #### Initialization Service initialization parameters are enumerated by `requires` and `recognizes`. `recognizes` parameters are optional. - class Foo::Client < Cistern::Service - requires :hmac_id, :hmac_secret - recognizes :url - end +```ruby +class Foo::Client < Cistern::Service + requires :hmac_id, :hmac_secret + recognizes :url +end - # Acceptable - Foo::Client.new(hmac_id: "1", hmac_secret: "2") # Foo::Client::Real - Foo::Client.new(hmac_id: "1", hmac_secret: "2", url: "http://example.org") # Foo::Client::Real +# Acceptable +Foo::Client.new(hmac_id: "1", hmac_secret: "2") # Foo::Client::Real +Foo::Client.new(hmac_id: "1", hmac_secret: "2", url: "http://example.org") # Foo::Client::Real - # ArgumentError - Foo::Client.new(hmac_id: "1", url: "http://example.org") - Foo::Client.new(hmac_id: "1") +# ArgumentError +Foo::Client.new(hmac_id: "1", url: "http://example.org") +Foo::Client.new(hmac_id: "1") +``` ### Mocking Cistern strongly encourages you to generate mock support for service. Mocking can be enabled using `mock!`. - Foo::Client.mocking? # falsey - real = Foo::Client.new # Foo::Client::Real - Foo::Client.mock! - Foo::Client.mocking? # true - fake = Foo::Client.new # Foo::Client::Mock - Foo::Client.unmock! - Foo::Client.mocking? # false - real.is_a?(Foo::Client::Real) # true - fake.is_a?(Foo::Client::Mock) # true +```ruby +Foo::Client.mocking? # falsey +real = Foo::Client.new # Foo::Client::Real +Foo::Client.mock! +Foo::Client.mocking? # true +fake = Foo::Client.new # Foo::Client::Mock +Foo::Client.unmock! +Foo::Client.mocking? # false +real.is_a?(Foo::Client::Real) # true +fake.is_a?(Foo::Client::Mock) # true +``` #### Data A uniform interface for mock data is mixed into the `Mock` class by default. - Foo::Client.mock! - client = Foo::Client.new # Foo::Client::Mock - client.data # Cistern::Data::Hash - client.data["bars"] += ["x"] # ["x"] +```ruby +Foo::Client.mock! +client = Foo::Client.new # Foo::Client::Mock +client.data # Cistern::Data::Hash +client.data["bars"] += ["x"] # ["x"] +``` Mock data is class-level by default - Foo::Client::Mock.data["bars"] # ["x"] +```ruby +Foo::Client::Mock.data["bars"] # ["x"] +``` `reset!` dimisses the `data` object. - client.data.object_id # 70199868585600 - client.reset! - client.data["bars"] # [] - client.data.object_id # 70199868566840 +```ruby +client.data.object_id # 70199868585600 +client.reset! +client.data["bars"] # [] +client.data.object_id # 70199868566840 +``` `clear` removes existing keys and values but keeps the same object. - client.data["bars"] += ["y"] # ["y"] - client.data.object_id # 70199868378300 - client.clear - client.data["bars"] # [] +```ruby +client.data["bars"] += ["y"] # ["y"] +client.data.object_id # 70199868378300 +client.clear +client.data["bars"] # [] - client.data.object_id # 70199868566840 +client.data.object_id # 70199868566840 +``` * `store` and `[]=` write * `fetch` and `[]` read You can make the service bypass Cistern's mock data structures by simply creating a `self.data` function in your service `Mock` declaration. - class Foo::Client < Cistern::Service - class Mock - def self.data - @data ||= {} - end - end +```ruby +class Foo::Client < Cistern::Service + class Mock + def self.data + @data ||= {} end + end +end +``` #### Requests Mock requests should be defined within the contextual `Mock` module and interact with the `data` object directly. - # lib/foo/requests/create_bar.rb - class Foo::Client - class Mock - def create_bar(options={}) - id = Foo.random_hex(6) +```ruby +# lib/foo/requests/create_bar.rb +class Foo::Client + class Mock + def create_bar(options={}) + id = Foo.random_hex(6) - bar = { - "id" => id - }.merge(options) + bar = { + "id" => id + }.merge(options) - self.data[:bars][id] = bar + self.data[:bars][id] = bar - response( - :body => {"bar" => bar}, - :status => 201, - :path => '/bar', - ) - end - end # Mock - end # Foo::Client + response( + :body => {"bar" => bar}, + :status => 201, + :path => '/bar', + ) + end + end # Mock +end # Foo::Client +``` - #### Storage Currently supported storage backends are: * `:hash` : `Cistern::Data::Hash` (default) * `:redis` : `Cistern::Data::Redis` Backends can be switched by using `store_in`. - # use redis with defaults - Patient::Mock.store_in(:redis) - # use redis with a specific client - Patient::Mock.store_in(:redis, client: Redis::Namespace.new("cistern", redis: Redis.new(host: "10.1.0.1")) - # use a hash - Patient::Mock.store_in(:hash) +```ruby +# use redis with defaults +Patient::Mock.store_in(:redis) +# use redis with a specific client +Patient::Mock.store_in(:redis, client: Redis::Namespace.new("cistern", redis: Redis.new(host: "10.1.0.1")) +# use a hash +Patient::Mock.store_in(:hash) +``` ### Model * `connection` represents the associated `Foo::Client` instance. * `collection` represents the related collection (if applicable) Example - class Foo::Client::Bar < Cistern::Model - identity :id +```ruby +class Foo::Client::Bar < Cistern::Model + identity :id - attribute :flavor - attribute :keypair_id, aliases: "keypair", squash: "id" - attribute :private_ips, type: :array + attribute :flavor + attribute :keypair_id, aliases: "keypair", squash: "id" + attribute :private_ips, type: :array - def destroy - params = { - "id" => self.identity - } - self.connection.destroy_bar(params).body["request"] - end + def destroy + params = { + "id" => self.identity + } + self.connection.destroy_bar(params).body["request"] + end - def save - requires :keypair_id + def save + requires :keypair_id - params = { - "keypair" => self.keypair_id, - "bar" => { - "flavor" => self.flavor, - }, - } + params = { + "keypair" => self.keypair_id, + "bar" => { + "flavor" => self.flavor, + }, + } - if new_record? - merge_attributes(connection.create_bar(params).body["bar"]) - else - requires :identity + if new_record? + merge_attributes(connection.create_bar(params).body["bar"]) + else + requires :identity - merge_attributes(connection.update_bar(params).body["bar"]) - end - end + merge_attributes(connection.update_bar(params).body["bar"]) end + end +end +``` +#### Dirty + +Dirty attributes are tracked and cleared when `merge_attributes` is called. + +* `changed` returns a Hash of changed attributes mapped to there initial value and current value +* `dirty_attributes` returns Hash of changed attributes with there current value. This should be used in the model `save` function. + + +```ruby +bar = Foo::Client::Bar.new(id: 1, flavor: "x") # => <#Foo::Client::Bar> + +bar.dirty? # => false +bar.changed # => {} +bar.dirty_attributes # => {} + +bar.flavor = "y" + +bar.dirty? # => true +bar.changed # => {flavor: ["x", "y"]} +bar.dirty_attributes # => {flavor: "y"} + +bar.save +bar.dirty? # => false +bar.changed # => {} +bar.dirty_attributes # => {} +``` + ### Collection `model` tells Cistern which class is contained within the collection. `Cistern::Collection` inherits from `Array` and lazy loads where applicable. - class Foo::Client::Bars < Cistern::Collection +```ruby +class Foo::Client::Bars < Cistern::Collection - model Foo::Client::Bar + model Foo::Client::Bar - def all(params = {}) - response = connection.get_bars(params) + def all(params = {}) + response = connection.get_bars(params) - data = response.body + data = response.body - self.load(data["bars"]) # store bar records in collection - self.merge_attributes(data) # store any other attributes of the response on the collection - end + self.load(data["bars"]) # store bar records in collection + self.merge_attributes(data) # store any other attributes of the response on the collection + end - def discover(provisioned_id, options={}) - params = { - "provisioned_id" => provisioned_id, - } - params.merge!("location" => options[:location]) if options.key?(:location) + def discover(provisioned_id, options={}) + params = { + "provisioned_id" => provisioned_id, + } + params.merge!("location" => options[:location]) if options.key?(:location) - connection.requests.new(connection.discover_bar(params).body["request"]) - end + connection.requests.new(connection.discover_bar(params).body["request"]) + end - def get(id) - if data = connection.get_bar("id" => id).body["bar"] - new(data) - else - nil - end - end + def get(id) + if data = connection.get_bar("id" => id).body["bar"] + new(data) + else + nil end + end +end +``` ### Request - module Foo - class Client - class Real - def create_bar(options={}) - request( - :body => {"bar" => options}, - :method => :post, - :path => '/bar' - ) - end - end # Real +```ruby +module Foo + class Client + class Real + def create_bar(options={}) + request( + :body => {"bar" => options}, + :method => :post, + :path => '/bar' + ) + end + end # Real - class Mock - def create_bar(options={}) - id = Foo.random_hex(6) + class Mock + def create_bar(options={}) + id = Foo.random_hex(6) - bar = { - "id" => id - }.merge(options) + bar = { + "id" => id + }.merge(options) - self.data[:bars][id]= bar + self.data[:bars][id]= bar - response( - :body => {"bar" => bar}, - :status => 201, - :path => '/bar', - ) - end - end # Mock - end # Client - end # Foo + response( + :body => {"bar" => bar}, + :status => 201, + :path => '/bar', + ) + end + end # Mock + end # Client +end # Foo +``` ## Examples * [zendesk2](https://github.com/lanej/zendesk2)