Permalink
Browse files

First attempt at making Her play better with ActiveModel

  • Loading branch information...
1 parent 50578bd commit de5c9dc90339c16a8c51d75ce8dd446e4122aef3 @remiprev committed Jan 18, 2013
Showing with 24 additions and 546 deletions.
  1. +1 −0 her.gemspec
  2. +9 −2 lib/her/model.rb
  3. +0 −114 lib/her/model/hooks.rb
  4. +13 −22 lib/her/model/orm.rb
  5. +0 −406 spec/model/hooks_spec.rb
  6. +1 −2 spec/model/orm_spec.rb
View
1 her.gemspec
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "rspec", "~> 2.13"
s.add_development_dependency "mocha", "~> 0.13"
+ s.add_runtime_dependency "activemodel", ">= 3.0.0"
s.add_runtime_dependency "activesupport", ">= 3.0.0"
s.add_runtime_dependency "faraday", "~> 0.8"
s.add_runtime_dependency "multi_json", "~> 1.5"
View
11 lib/her/model.rb
@@ -2,10 +2,10 @@
require "her/model/http"
require "her/model/orm"
require "her/model/relationships"
-require "her/model/hooks"
require "her/model/introspection"
require "her/model/paths"
require "her/model/nested_attributes"
+require "active_model"
module Her
# This module is the main element of Her. After creating a Her::API object,
@@ -27,12 +27,19 @@ module Model
include Her::Model::Paths
include Her::Model::Relationships
include Her::Model::NestedAttributes
+ include ActiveModel::Validations
+ include ActiveModel::Conversion
+ include ActiveModel::Dirty
# Class methods
included do
extend Her::Model::Base
extend Her::Model::HTTP
- extend Her::Model::Hooks
+ extend ActiveModel::Naming
+ extend ActiveModel::Translation
+
+ extend ActiveModel::Callbacks
+ define_model_callbacks :create, :update, :save, :find, :destroy
# Define default settings
root_element self.name.split("::").last.underscore
View
114 lib/her/model/hooks.rb
@@ -1,114 +0,0 @@
-module Her
- module Model
- # Her supports hooks/callbacks that are triggered whenever resources are created, updated or destroyed.
- #
- # @example Defining a hook with a block
- # class User
- # include Her::Model
- # before_save { |resource| resource.internal_id = 42 }
- # end
- #
- # @example Defining a hook with a method name
- # class User
- # include Her::Model
- # before_save :set_internal_id
- #
- # private
- # def set_internal_id
- # self.internal_id = 42
- # end
- # end
- module Hooks
- # Add a *before save* callback. Triggered before a resource is created or updated.
- # @param [Symbol, &block] method A method or a block to be called
- def before_save(method=nil, &block); set_hook(:before, :save, method || block); end
-
- # Add a *before create* callback. Triggered before a resource is created.
- # @param [Symbol, &block] method A method or a block to be called
- def before_create(method=nil, &block); set_hook(:before, :create, method || block); end
-
- # Add a *before update* callback. Triggered before a resource is updated.
- # @param [Symbol, &block] method A method or a block to be called
- def before_update(method=nil, &block); set_hook(:before, :update, method || block); end
-
- # Add a *before destroy* callback. Triggered before a resource is destroyed.
- # @param [Symbol, &block] method A method or a block to be called
- def before_destroy(method=nil, &block); set_hook(:before, :destroy, method || block); end
-
- # Do not add a *before find* callback. Only *after find* is supported.
- def before_find(method=nil, &block); raise NoMethodError, "undefined method `before_find' for #{self}"; end
-
- # Add a *after save* callback. Triggered after a resource is created or updated.
- # @param [Symbol, &block] method A method or a block to be called
- def after_save(method=nil, &block); set_hook(:after, :save, method || block); end
-
- # Add a *after create* callback. Triggered after a resource is created.
- # @param [Symbol, &block] method A method or a block to be called
- def after_create(method=nil, &block); set_hook(:after, :create, method || block); end
-
- # Add a *after update* callback. Triggered after a resource is updated.
- # @param [Symbol, &block] method A method or a block to be called
- def after_update(method=nil, &block); set_hook(:after, :update, method || block); end
-
- # Add a *after destroy* callback. Triggered after a resource is destroyed.
- # @param [Symbol, &block] method A method or a block to be called
- def after_destroy(method=nil, &block); set_hook(:after, :destroy, method || block); end
-
- # Add a *after find* callback. Triggered after a resource is found.
- # @param [Symbol, &block] method A method or a block to be called
- def after_find(method=nil, &block); set_hook(:after, :find, method || block); end
-
- # Wrap a block between “before” and “after” hooks
- # @private
- def wrap_in_hooks(resource, *hooks)
- perform_before_hooks(resource, *hooks)
- yield(resource, resource.class) if block_given?
- perform_after_hooks(resource, *hooks.reverse)
- end
-
- # @private
- def hooks
- @her_hooks ||= begin
- if superclass.respond_to?(:hooks)
- superclass.hooks.dup
- else
- {}
- end
- end
- end
-
- private
- # @private
- def set_hook(time, name, action)
- (self.hooks["#{time}_#{name}".to_sym] ||= []) << action
- end
-
- # @private
- def perform_hook(record, time, name)
- Array(self.hooks["#{time}_#{name}".to_sym]).each do |hook|
- if hook.is_a? Symbol
- record.send(hook)
- else
- hook.call(record)
- end
- end
- end
-
- # Perform “after” hooks on a resource
- # @private
- def perform_after_hooks(resource, *hooks)
- hooks.each do |hook|
- perform_hook(resource, :after, hook)
- end
- end
-
- # Perform “before” hooks on a resource
- # @private
- def perform_before_hooks(resource, *hooks)
- hooks.each do |hook|
- perform_hook(resource, :before, hook)
- end
- end
- end
- end
-end
View
35 lib/her/model/orm.rb
@@ -3,14 +3,14 @@ module Model
# This module adds ORM-like capabilities to the model
module ORM
extend ActiveSupport::Concern
- attr_accessor :data, :metadata, :errors
+ attr_accessor :data, :metadata, :response_errors
alias :attributes :data
alias :attributes= :data=
# Initialize a new object with data received from an HTTP request
def initialize(params={})
@metadata = params.delete(:_metadata) || {}
- @errors = params.delete(:_errors) || {}
+ @response_errors = params.delete(:_errors) || {}
update_data(params)
end
@@ -20,7 +20,7 @@ def initialize(params={})
def self.initialize_collection(klass, parsed_data={})
collection_data = parsed_data[:data].map do |item_data|
resource = klass.new(klass.parse(item_data))
- klass.wrap_in_hooks(resource, :find)
+ resource.run_callbacks :find
resource
end
Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
@@ -92,16 +92,6 @@ def new?
!@data.include?(:id)
end
- # Return `true` if a resource does not contain errors
- def valid?
- @errors.empty?
- end
-
- # Return `true` if a resource contains errors
- def invalid?
- @errors.any?
- end
-
# Return `true` if the other object is also a Her::Model and has matching data
def ==(other)
other.is_a?(Her::Model) && @data == other.data
@@ -143,13 +133,13 @@ def save
method = :post
end
- self.class.wrap_in_hooks(resource, *hooks) do |resource, klass|
- klass.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data|
+ run_callbacks(*hooks) do
+ self.class.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data, response|
update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
self.metadata = parsed_data[:metadata]
- self.errors = parsed_data[:errors]
+ self.response_errors = parsed_data[:errors]
- return false if self.errors.any?
+ return false if self.response_errors.any?
end
end
@@ -164,11 +154,11 @@ def save
# # Called via DELETE "/users/1"
def destroy
resource = self
- self.class.wrap_in_hooks(resource, :destroy) do |resource, klass|
- klass.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data|
+ run_callbacks :destroy do
+ self.class.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data, response|
update_data(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
self.metadata = parsed_data[:metadata]
- self.errors = parsed_data[:errors]
+ self.response_errors = parsed_data[:errors]
end
end
self
@@ -232,6 +222,7 @@ def find(*ids)
request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data, response|
if response.success?
resource = new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
+ resource.run_callbacks :find
else
return nil
end
@@ -264,14 +255,14 @@ def all(params={})
# # Called via POST "/users/1"
def create(params={})
resource = new(params)
- wrap_in_hooks(resource, :create, :save) do |resource, klass|
+ resource.run_callbacks :create, :save do
params = resource.to_params
request(params.merge(:_method => :post, :_path => "#{build_request_path(params)}")) do |parsed_data, response|
data = parse(parsed_data[:data])
resource.instance_eval do
update_data(data)
@metadata = parsed_data[:metadata]
- @errors = parsed_data[:errors]
+ @response_errors = parsed_data[:errors]
end
end
end
View
406 spec/model/hooks_spec.rb
@@ -1,406 +0,0 @@
-# encoding: utf-8
-require File.join(File.dirname(__FILE__), "../spec_helper.rb")
-
-describe Her::Model::Hooks do
- context "adding hooks to a model" do
- before do
- spawn_model "Foo::User"
- end
-
- describe "method hooks" do
- it "handles “before save” method hooks" do
- Foo::User.before_save :set_internal_id
- Foo::User.hooks[:before_save].length.should == 1
- Foo::User.hooks[:before_save].first.class.should == Symbol
- end
-
- it "handles “before create” method hooks" do
- Foo::User.before_create :set_internal_id
- Foo::User.hooks[:before_create].length.should == 1
- Foo::User.hooks[:before_create].first.class.should == Symbol
- end
-
- it "handles “before update” method hooks" do
- Foo::User.before_update :set_internal_id
- Foo::User.hooks[:before_update].length.should == 1
- Foo::User.hooks[:before_update].first.class.should == Symbol
- end
-
- it "handles “before destroy” method hooks" do
- Foo::User.before_destroy :set_internal_id
- Foo::User.hooks[:before_destroy].length.should == 1
- Foo::User.hooks[:before_destroy].first.class.should == Symbol
- end
-
- it "does not handle “before find” method hooks" do
- expect {
- Foo::User.before_find :set_internal_id
- }.should raise_error(NoMethodError)
- end
-
- it "handles “after save” method hooks" do
- Foo::User.after_save :set_internal_id
- Foo::User.hooks[:after_save].length.should == 1
- Foo::User.hooks[:after_save].first.class.should == Symbol
- end
-
- it "handles “after create” method hooks" do
- Foo::User.after_create :set_internal_id
- Foo::User.hooks[:after_create].length.should == 1
- Foo::User.hooks[:after_create].first.class.should == Symbol
- end
-
- it "handles “after update” method hooks" do
- Foo::User.after_update :set_internal_id
- Foo::User.hooks[:after_update].length.should == 1
- Foo::User.hooks[:after_update].first.class.should == Symbol
- end
-
- it "handles “after destroy” method hooks" do
- Foo::User.after_destroy :set_internal_id
- Foo::User.hooks[:after_destroy].length.should == 1
- Foo::User.hooks[:after_destroy].first.class.should == Symbol
- end
-
- it "handles “after find” method hooks" do
- Foo::User.after_find :set_internal_id
- Foo::User.hooks[:after_find].length.should == 1
- Foo::User.hooks[:after_find].first.class.should == Symbol
- end
- end
-
- describe "block hooks" do
- it "handles “before save” block hooks" do
- Foo::User.before_save { |record| record.internal_id = 42 }
- Foo::User.hooks[:before_save].length.should == 1
- Foo::User.hooks[:before_save].first.class.should == Proc
- end
-
- it "handles “before create” block hooks" do
- Foo::User.before_create { |record| record.internal_id = 42 }
- Foo::User.hooks[:before_create].length.should == 1
- Foo::User.hooks[:before_create].first.class.should == Proc
- end
-
- it "handles “before update” block hooks" do
- Foo::User.before_update { |record| record.internal_id = 42 }
- Foo::User.hooks[:before_update].length.should == 1
- Foo::User.hooks[:before_update].first.class.should == Proc
- end
-
- it "handles “before destroy” block hooks" do
- Foo::User.before_destroy { |record| record.internal_id = 42 }
- Foo::User.hooks[:before_destroy].length.should == 1
- Foo::User.hooks[:before_destroy].first.class.should == Proc
- end
-
- it "does not handle “before find” block hooks" do
- expect {
- Foo::User.before_find :set_internal_id
- }.should raise_error(NoMethodError)
- end
-
- it "handles “after save” block hooks" do
- Foo::User.after_save { |record| record.internal_id = 42 }
- Foo::User.hooks[:after_save].length.should == 1
- Foo::User.hooks[:after_save].first.class.should == Proc
- end
-
- it "handles “after create” block hooks" do
- Foo::User.after_create { |record| record.internal_id = 42 }
- Foo::User.hooks[:after_create].length.should == 1
- Foo::User.hooks[:after_create].first.class.should == Proc
- end
-
- it "handles “after update” block hooks" do
- Foo::User.after_update { |record| record.internal_id = 42 }
- Foo::User.hooks[:after_update].length.should == 1
- Foo::User.hooks[:after_update].first.class.should == Proc
- end
-
- it "handles “after destroy” block hooks" do
- Foo::User.after_destroy { |record| record.internal_id = 42 }
- Foo::User.hooks[:after_destroy].length.should == 1
- Foo::User.hooks[:after_destroy].first.class.should == Proc
- end
-
- it "handles “after find” block hooks" do
- Foo::User.after_find { |record| record.internal_id = 42 }
- Foo::User.hooks[:after_find].length.should == 1
- Foo::User.hooks[:after_find].first.class.should == Proc
- end
- end
-
- context "inheriting hooks from a superclass" do
- it "copies hooks to the subclass" do
- Foo::User.before_save :set_internal_id
- Foo::User.after_create { |record| record.internal_id = 42 }
- subclass = Class.new(Foo::User)
- subclass.hooks.object_id.should_not == Foo::User.hooks.object_id
- subclass.hooks[:before_save].should == [:set_internal_id]
- subclass.hooks[:after_create].length.should == 1
- end
- end
- end
-
- context "perform hooks on a model" do
- before do
- Her::API.setup :url => "https://api.example.com" do |builder|
- builder.use Her::Middleware::FirstLevelParseJSON
- builder.use Faraday::Request::UrlEncoded
- builder.adapter :test do |stub|
- stub.get("/users") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
- stub.post("/users") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json] }
- stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json] }
- stub.get("/users/2") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke" }.to_json] }
- stub.put("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json] }
- stub.delete("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json] }
- end
- end
-
- spawn_model "Foo::User" do
- attr_accessor :internal_save_id, :internal_create_id, :internal_update_id, :internal_destroy_id
- attr_accessor :internal_after_save_id, :internal_after_create_id, :internal_after_update_id, :internal_after_destroy_id
- attr_accessor :internal_after_find_id
-
- def change_internal_save_id; @internal_save_id = 100; end
- def change_internal_create_id; @internal_create_id = 101; end
- def change_internal_update_id; @internal_update_id = 102; end
- def change_internal_destroy_id; @internal_destroy_id = 103; end
-
- def change_internal_after_save_id; @internal_after_save_id = 100; end
- def change_internal_after_create_id; @internal_after_create_id = 101; end
- def change_internal_after_update_id; @internal_after_update_id = 102; end
- def change_internal_after_destroy_id; @internal_after_destroy_id = 103; end
-
- def change_internal_after_find_id; @internal_after_find_id = 104; end
- end
- end
-
- describe "method hooks" do
- before do
- Foo::User.before_save :change_internal_save_id
- Foo::User.before_update :change_internal_update_id
- Foo::User.before_create :change_internal_create_id
- Foo::User.before_destroy :change_internal_destroy_id
-
- Foo::User.after_save :change_internal_after_save_id
- Foo::User.after_update :change_internal_after_update_id
- Foo::User.after_create :change_internal_after_create_id
- Foo::User.after_destroy :change_internal_after_destroy_id
-
- Foo::User.after_find :change_internal_after_find_id
- end
-
- it "perform “before save” “before create” method hook on Model#save without an ID" do
- @user = Foo::User.new(:fullname => "Tobias Fünke")
- @user.save
- @user.internal_save_id.should == 100
- @user.internal_create_id.should == 101
- @user.internal_update_id.should == nil
- @user.internal_after_find_id.should == nil
- end
-
- it "perform “before save” and “before update” method hook on Model#save with an ID" do
- @user = Foo::User.find(1)
- @user.save
- @user.internal_save_id.should == 100
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == 102
- @user.internal_after_find_id.should == 104
- end
-
- it "perform “before destroy” method hook on Model#destroy" do
- @user = Foo::User.find(1)
- @user.destroy
- @user.internal_save_id.should == nil
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == nil
- @user.internal_destroy_id.should == 103
- @user.internal_after_find_id.should == 104
- end
-
- it "perform “after save” “after create” method hook on Model#save without an ID" do
- @user = Foo::User.new(:fullname => "Tobias Fünke")
- @user.save
- @user.internal_after_save_id.should == 100
- @user.internal_after_create_id.should == 101
- @user.internal_after_update_id.should == nil
- @user.internal_after_find_id.should == nil
- end
-
- it "perform “after save” “after update” method hook on Model#save with an ID" do
- @user = Foo::User.find(1)
- @user.save
- @user.internal_after_save_id.should == 100
- @user.internal_after_create_id.should == nil
- @user.internal_after_update_id.should == 102
- @user.internal_after_find_id.should == 104
- end
-
- it "perform “after save” “after update” method hook on Model.save_existing" do
- @user = Foo::User.save_existing(1, { :fullname => "Tobias Fünke" })
- @user.internal_after_save_id.should == 100
- @user.internal_after_create_id.should == nil
- @user.internal_after_update_id.should == 102
- @user.internal_after_find_id.should == nil
- end
-
- it "perform “after save” “after create” method hook on Model.create" do
- @user = Foo::User.create({ :fullname => "Tobias Fünke" })
- @user.internal_after_save_id.should == 100
- @user.internal_after_create_id.should == 101
- @user.internal_after_update_id.should == nil
- @user.internal_after_find_id.should == nil
- end
-
- it "perform “after find” method hook on Model.find" do
- @user = Foo::User.find(1)
- @user.internal_after_find_id.should == 104
- @user.internal_save_id.should == nil
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == nil
- @user.internal_destroy_id.should == nil
- end
-
- it "perform “after find” method hook on Model#find with IDs" do
- @users = Foo::User.find([1,2])
- @users.each do |user|
- user.internal_after_find_id.should == 104
- user.internal_save_id.should == nil
- user.internal_create_id.should == nil
- user.internal_update_id.should == nil
- user.internal_destroy_id.should == nil
- end
- end
-
- it "perform “after find” method hook on Model.all" do
- @users = Foo::User.all
- @users.each do |user|
- user.internal_after_find_id.should == 104
- user.internal_save_id.should == nil
- user.internal_create_id.should == nil
- user.internal_update_id.should == nil
- user.internal_destroy_id.should == nil
- end
- end
- end
-
- describe "block hooks" do
- before do
- Foo::User.before_save { |record| record.internal_save_id = 200 }
- Foo::User.before_create { |record| record.internal_create_id = 201 }
- Foo::User.before_update { |record| record.internal_update_id = 202 }
- Foo::User.before_destroy { |record| record.internal_destroy_id = 203 }
-
- Foo::User.after_save { |record| record.internal_after_save_id = 200 }
- Foo::User.after_create { |record| record.internal_after_create_id = 201 }
- Foo::User.after_update { |record| record.internal_after_update_id = 202 }
- Foo::User.after_destroy { |record| record.internal_after_destroy_id = 203 }
- Foo::User.after_find { |record| record.internal_after_find_id = 204 }
- end
-
- it "perform “before save” and “before create” block hook on Model#save without an ID" do
- @user = Foo::User.new(:fullname => "Tobias Fünke")
- @user.save
- @user.internal_save_id.should == 200
- @user.internal_create_id.should == 201
- @user.internal_update_id.should == nil
- @user.internal_after_find_id.should == nil
- end
-
- it "perform “before save” and “before update” block hook on Model#save with an ID" do
- @user = Foo::User.find(1)
- @user.save
- @user.internal_save_id.should == 200
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == 202
- @user.internal_after_find_id.should == 204
- end
-
- it "perform “before destroy” block hook on Model#destroy" do
- @user = Foo::User.find(1)
- @user.destroy
- @user.internal_save_id.should == nil
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == nil
- @user.internal_destroy_id.should == 203
- @user.internal_after_find_id.should == 204
- end
-
- it "perform “after save” “after create” block hook on Model#save without an ID" do
- @user = Foo::User.new(:fullname => "Tobias Fünke")
- @user.save
- @user.internal_after_save_id.should == 200
- @user.internal_after_create_id.should == 201
- @user.internal_after_update_id.should == nil
- @user.internal_after_find_id.should == nil
- end
-
- it "perform “after save” “after update” block hook on Model#save with an ID" do
- @user = Foo::User.find(1)
- @user.save
- @user.internal_after_find_id.should == 204
- @user.internal_after_save_id.should == 200
- @user.internal_after_create_id.should == nil
- @user.internal_after_update_id.should == 202
- end
-
- it "perform “after find” block hook on Model#find" do
- @user = Foo::User.find(1)
- @user.internal_after_find_id.should == 204
- @user.internal_save_id.should == nil
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == nil
- end
-
- it "perform “after find” method hook on Model#find with IDs" do
- @users = Foo::User.find([1,2])
- @users.each do |user|
- user.internal_after_find_id.should == 204
- user.internal_save_id.should == nil
- user.internal_create_id.should == nil
- user.internal_update_id.should == nil
- end
- end
-
- it "perform “after find” method hook on Model.all" do
- @users = Foo::User.all
- @users.each do |user|
- user.internal_after_find_id.should == 204
- user.internal_save_id.should == nil
- user.internal_create_id.should == nil
- user.internal_update_id.should == nil
- end
- end
-
- it "perform “after find” block hook on Model#find" do
- @user = Foo::User.find(1)
- @user.internal_after_find_id.should == 204
- @user.internal_save_id.should == nil
- @user.internal_create_id.should == nil
- @user.internal_update_id.should == nil
- end
-
- it "perform “after find” method hook on Model#find with IDs" do
- @users = Foo::User.find([1,2])
- @users.each do |user|
- user.internal_after_find_id.should == 204
- user.internal_save_id.should == nil
- user.internal_create_id.should == nil
- user.internal_update_id.should == nil
- end
- end
-
- it "perform “after find” method hook on Model.all" do
- @users = Foo::User.all
- @users.each do |user|
- user.internal_after_find_id.should == 204
- user.internal_save_id.should == nil
- user.internal_create_id.should == nil
- user.internal_update_id.should == nil
- end
- end
- end
- end
-end
View
3 spec/model/orm_spec.rb
@@ -133,8 +133,7 @@
it "handles error data on a resource" do
@user = User.create(:name => "George Michael Bluth")
- @user.errors.should == ["Yes", "Sir"]
- @user.should be_invalid
+ @user.response_errors.should == ["Yes", "Sir"]
end
end

0 comments on commit de5c9dc

Please sign in to comment.