imperator-ext 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "imperator-ext"
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kristian Mandrup"]
12
+ s.date = "2012-08-14"
13
+ s.description = "Factories, Macros, REST helpers and Mongoid integration"
14
+ s.email = "kmandrup@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "docs/Design.md",
29
+ "imperator-ext.gemspec",
30
+ "lib/imperator-ext.rb",
31
+ "lib/imperator/command-ext.rb",
32
+ "lib/imperator/command/class_factory.rb",
33
+ "lib/imperator/command/macros.rb",
34
+ "lib/imperator/command/method_factory.rb",
35
+ "lib/imperator/command/rest.rb",
36
+ "lib/imperator/command/rest_helper.rb",
37
+ "lib/imperator/factory.rb",
38
+ "lib/imperator/mongoid.rb",
39
+ "lib/imperator/mongoid/attribute_helper.rb",
40
+ "lib/imperator/mongoid/command.rb",
41
+ "lib/imperator/mongoid/command/rest.rb",
42
+ "spec/imperator-ext/command/class_factory_spec.rb",
43
+ "spec/imperator-ext/command/macros_spec.rb",
44
+ "spec/imperator-ext/command/method_factory_spec.rb",
45
+ "spec/imperator-ext/command/rest_helper_spec.rb",
46
+ "spec/imperator-ext/command/rest_spec.rb",
47
+ "spec/imperator-ext/mongoid/command_spec.rb",
48
+ "spec/imperator-ext/mongoid/rest_command_spec.rb",
49
+ "spec/imperator-ext/shared_ex/attribute_helper_ex.rb",
50
+ "spec/imperator-ext/shared_ex/command_ex.rb",
51
+ "spec/imperator-ext/shared_ex/rest_helper_ex.rb",
52
+ "spec/spec_helper.rb"
53
+ ]
54
+ s.homepage = "http://github.com/kristianmandrup/imperator-ext"
55
+ s.licenses = ["MIT"]
56
+ s.require_paths = ["lib"]
57
+ s.rubygems_version = "1.8.24"
58
+ s.summary = "Various extensions for Imperator"
59
+
60
+ if s.respond_to? :specification_version then
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<virtus>, [">= 0"])
65
+ s.add_runtime_dependency(%q<imperator>, [">= 0"])
66
+ s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
67
+ s.add_development_dependency(%q<rdoc>, [">= 3.12"])
68
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
69
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
70
+ s.add_development_dependency(%q<simplecov>, [">= 0.5"])
71
+ else
72
+ s.add_dependency(%q<virtus>, [">= 0"])
73
+ s.add_dependency(%q<imperator>, [">= 0"])
74
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
75
+ s.add_dependency(%q<rdoc>, [">= 3.12"])
76
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
77
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
78
+ s.add_dependency(%q<simplecov>, [">= 0.5"])
79
+ end
80
+ else
81
+ s.add_dependency(%q<virtus>, [">= 0"])
82
+ s.add_dependency(%q<imperator>, [">= 0"])
83
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
84
+ s.add_dependency(%q<rdoc>, [">= 3.12"])
85
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
86
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
87
+ s.add_dependency(%q<simplecov>, [">= 0.5"])
88
+ end
89
+ end
90
+
@@ -0,0 +1,121 @@
1
+ require 'imperator/command/rest_helper'
2
+ require 'imperator/command/rest'
3
+
4
+ class Imperator::Command
5
+ # http://johnragan.wordpress.com/2010/02/18/ruby-metaprogramming-dynamically-defining-classes-and-methods/
6
+ class ClassFactory
7
+ class << self
8
+ def use &block
9
+ yield self
10
+ end
11
+
12
+ def default_parent clazz
13
+ @default_parent ||= clazz
14
+ end
15
+
16
+ def get_default_parent
17
+ @default_parent ||= ::Imperator::Command
18
+ end
19
+
20
+ # Usage:
21
+ # Imperator::Command::ClassFactory.create :update, Post, parent: Imperator::Mongoid::Command do
22
+ # ..
23
+ # end
24
+ def build_command action, model, options = {}, &block
25
+ clazz_name = "#{action.to_s.camelize}#{model.to_s.camelize}Command"
26
+ parent = options[:parent] || get_default_parent
27
+ clazz = parent ? Class.new(parent) : Class.new
28
+ Object.const_set clazz_name, clazz
29
+ clazz = self.const_get(clazz_name)
30
+ if options[:auto_attributes]
31
+ clazz.instance_eval do
32
+ if respond_to? :attributes_for
33
+ attributes_for(model, :except => options[:except], :only => options[:only])
34
+ end
35
+ end
36
+ end
37
+ if block_given?
38
+ clazz.instance_eval &block
39
+ end
40
+ clazz
41
+ end
42
+
43
+ # Usage:
44
+ # Imperator::Command::ClassFactory.create_rest :all, Post, parent: Imperator::Mongoid::Command do
45
+ # ..
46
+ # end
47
+ def rest_command action, model, options = {}, &block
48
+ options.reverse_merge! default_options
49
+ options[:parent] ||= get_default_rest_class(model)
50
+ rest_commands_for(model, options, &block) and return if action.to_sym == :all
51
+ if rest_actions.include? action.to_sym
52
+ action_name = "#{action}_command_for"
53
+ send action_name, model, options, &block
54
+ else
55
+ raise ArgumentError, "Not a supported REST action. Must be one of #{rest_actions}, was #{action}"
56
+ end
57
+ end
58
+
59
+ attr_writer :default_rest_class
60
+
61
+ def get_default_rest_class model
62
+ model.ancestors.include?(Mongoid::Document) ? default_mongoid_rest_class : default_rest_class
63
+ end
64
+
65
+ def default_rest_class
66
+ @default_rest_class ||= Imperator::Command::Rest
67
+ end
68
+
69
+ def default_mongoid_rest_class
70
+ @default_mongoid_rest_class ||= Imperator::Mongoid::Command::Rest
71
+ end
72
+
73
+ def reset_rest_class
74
+ @default_rest_class = Imperator::Command::Rest
75
+ end
76
+
77
+ attr_writer :default_options
78
+
79
+ def default_options
80
+ @default_options ||= {}
81
+ end
82
+
83
+
84
+ protected
85
+
86
+ def rest_actions
87
+ [:create, :update, :delete]
88
+ end
89
+
90
+ def create_command_for model, options = {}, &block
91
+ options[:parent] ||= default_rest_class
92
+ c = build_command :create, model, options do
93
+ create_action
94
+ end
95
+ c.class_eval &block if block_given?
96
+ end
97
+
98
+ def update_command_for model, options = {}, &block
99
+ options[:parent] ||= default_rest_class
100
+ c = build_command :update, model, options do
101
+ update_action
102
+ end
103
+ c.class_eval &block if block_given?
104
+ end
105
+
106
+ def delete_command_for model, options = {}, &block
107
+ options[:parent] ||= default_rest_class
108
+ c = build_command :delete, model, options do
109
+ delete_action
110
+ end
111
+ c.class_eval &block if block_given?
112
+ end
113
+
114
+ def rest_commands_for model, options = {}, &block
115
+ rest_actions.each do |action|
116
+ send "#{action}_command_for", model, options, &block
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,13 @@
1
+ # Global macro methods
2
+
3
+ def imperator_class_factory
4
+ Imperator::Command::ClassFactory
5
+ end
6
+
7
+ def build_command action, model, options = {}, &block
8
+ imperator_class_factory.build_command action, model, options = {}, &block
9
+ end
10
+
11
+ def build_rest_command action, model, options = {}, &block
12
+ imperator_class_factory.rest_command action, model, options = {}, &block
13
+ end
@@ -0,0 +1,17 @@
1
+ class Imperator::Command
2
+ module MethodFactory
3
+ def command_method command, options = {}
4
+ namespace = (options[:ns] || '').to_s
5
+ namespace.sub! /Controller$/, ''
6
+ define_method "#{command}_command" do
7
+ instance_var = "@#{command}_command"
8
+ unless instance_variable_get(instance_var)
9
+ clazz = [namespace, "#{command.to_s.camelize}Command"].join('::').constantize
10
+ instance_variable_set instance_var, clazz.new(options.merge initiator: self)
11
+ end
12
+ end
13
+ end
14
+
15
+ extend self
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require 'imperator/command/rest_helper'
2
+
3
+ class Imperator::Command
4
+ class Rest < Imperator::Command
5
+ include Imperator::Command::RestHelper
6
+ end
7
+ end
@@ -0,0 +1,78 @@
1
+ class Imperator::ResourceNotFoundError < StandardError; end
2
+
3
+ class Imperator::Command
4
+ module RestHelper
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_writer :object_class
9
+ end
10
+
11
+ module ClassMethods
12
+ def create_action &block
13
+ action do
14
+ object_class.create attribute_set if object_class
15
+ instance_eval &block if block_given?
16
+ end
17
+ end
18
+
19
+ def update_action &block
20
+ action do
21
+ begin
22
+ find_object.update_attributes attribute_set if find_object
23
+ instance_eval &block if block_given?
24
+ rescue Imperator::ResourceNotFoundError => e
25
+ on_error e
26
+ end
27
+ end
28
+ end
29
+
30
+ def delete_action &block
31
+ action do
32
+ find_object.delete
33
+ instance_eval &block if block_given?
34
+ end
35
+ end
36
+
37
+ def on_error &block
38
+ define_method(:on_error, &block)
39
+ end
40
+
41
+ def for_class clazz
42
+ @object_class = clazz
43
+ end
44
+
45
+ def object_class
46
+ @object_class ||= filtered_class_name
47
+ end
48
+
49
+ protected
50
+
51
+ # convert to underscore format, fx UpdatePostCommand becomes update_post_command
52
+ # remove 'create', 'update' or 'delete' in the front of name: _post_command
53
+ # then remove command at the back: _post_
54
+ # then remove any '_': post
55
+ def filtered_class_name
56
+ self.class.name.underscore.sub(/^(create|update|delete)/, '').sub(/command$/, '').sub(/_/, '')
57
+ end
58
+ end
59
+
60
+ def object_class
61
+ self.class.object_class
62
+ end
63
+
64
+ def on_error exception
65
+ raise Imperator::InvalidCommandError, "The Command #{self} caused an error: #{exception}"
66
+ end
67
+
68
+ def find_object
69
+ object ||= object_class.find(self.id)
70
+ rescue
71
+ find_object_error
72
+ end
73
+
74
+ def find_object_error
75
+ raise Imperator::ResourceNotFoundError, "The resource #{self.id} could not be found"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,14 @@
1
+ class Imperator::Command
2
+ # the main object this command works on
3
+ attribute :object, Object
4
+
5
+ # object that initiated the command.
6
+ # Can be used for method delegation etc
7
+ attribute :initiator, Object
8
+
9
+ def to_s
10
+ str = "Command: #{id}"
11
+ str << " - #{object}" if object
12
+ str
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ require 'imperator/command/class_factory'
2
+ require 'imperator/command/method_factory'
3
+
@@ -0,0 +1,29 @@
1
+ module Imperator
2
+ module Mongoid
3
+ module AttributeHelper
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def attributes_for clazz, options = {}
8
+ use_attributes = clazz.attribute_names
9
+
10
+ unless options[:except].blank?
11
+ use_attributes = use_attributes - [options[:except]].flatten.map(&:to_s)
12
+ end
13
+ unless options[:only].blank?
14
+ use_attributes = use_attributes & [options[:only]].flatten.map(&:to_s) # intersection
15
+ end
16
+
17
+ clazz.fields.each do |key, field|
18
+ name = key
19
+ type = field.type
20
+ # skip if this field is excluded for use in command
21
+ next unless use_attributes.include? name.to_s
22
+
23
+ attribute field.name, field.type, :default => field.default_val
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require 'imperator/mongoid/command'
2
+
3
+ module Imperator::Mongoid
4
+ class Command < Imperator::Command
5
+ class Rest < Command
6
+ include Imperator::Command::RestHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'imperator/mongoid/attribute_helper'
2
+
3
+ # Usage
4
+
5
+ # class UpdatePostCommand < Imperator::Mongoid::Command
6
+ # attributes_for Post, except: [:state]
7
+ # attributes :title, :author
8
+
9
+ # validates_presence_of :object
10
+
11
+ # action do
12
+ # object = SomeObject.find(id)
13
+ # object.authored_by(author)
14
+ # end
15
+ # end
16
+
17
+ module Imperator::Mongoid
18
+ class Command < Imperator::Command
19
+ include Imperator::Mongoid::AttributeHelper
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Imperator
2
+ module Mongoid
3
+ end
4
+ end
5
+
6
+ require 'imperator/mongoid/command/rest'
@@ -0,0 +1,5 @@
1
+ require 'imperator'
2
+ require 'imperator/factory'
3
+ require 'imperator/command-ext'
4
+
5
+ require 'imperator/mongoid' if defined?(Mongoid)
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ class UpdateRestCommand < Imperator::Command::Rest
4
+ end
5
+ class CreateRestCommand < Imperator::Command::Rest
6
+ end
7
+ class DeleteRestCommand < Imperator::Command::Rest
8
+ end
9
+
10
+ class Post
11
+ end
12
+
13
+ class Article
14
+ end
15
+
16
+ class Account
17
+ end
18
+
19
+ class User
20
+ end
21
+
22
+ class Foo
23
+ end
24
+
25
+ class Payment
26
+ end
27
+
28
+ describe Imperator::Command::ClassFactory do
29
+ subject { Imperator::Command::ClassFactory }
30
+
31
+ describe '.rest_command' do
32
+ context 'update' do
33
+ before :all do
34
+ subject.default_rest_class = Imperator::Command::Rest
35
+ subject.rest_command :update, Article, :parent => UpdateRestCommand, :auto_attributes => true do
36
+ def hello
37
+ "hello"
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'UpdateArticleCommand created' do
43
+ let(:command) { UpdateArticleCommand.new }
44
+
45
+ specify { UpdateArticleCommand.superclass.should == UpdateRestCommand }
46
+ specify { command.hello.should == "hello" }
47
+ end
48
+ end
49
+
50
+ context 'create' do
51
+ before :all do
52
+ subject.default_rest_class = Imperator::Command::Rest
53
+ subject.rest_command :create, Article, :parent => CreateRestCommand, :auto_attributes => true do
54
+ def hello
55
+ "hello"
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'CreateArticleCommand created' do
61
+ let(:command) { CreateArticleCommand.new }
62
+
63
+ specify { CreateArticleCommand.superclass.should == CreateRestCommand }
64
+ specify { command.hello.should == "hello" }
65
+ end
66
+ end
67
+
68
+ context 'delete' do
69
+ before :all do
70
+ subject.default_rest_class = Imperator::Command::Rest
71
+ subject.rest_command :delete, Article, :parent => DeleteRestCommand, :auto_attributes => true do
72
+ def hello
73
+ "hello"
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'DeleteArticleCommand created' do
79
+ let(:command) { DeleteArticleCommand.new }
80
+
81
+ specify { DeleteArticleCommand.superclass.should == DeleteRestCommand }
82
+ specify { command.hello.should == "hello" }
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ describe Imperator::Command::ClassFactory do
89
+
90
+ subject { Imperator::Command::ClassFactory }
91
+
92
+ describe '.build_command' do
93
+ before :all do
94
+ subject.build_command :show, Post
95
+ end
96
+
97
+ its(:get_default_parent) { should == Imperator::Command }
98
+ specify { ShowPostCommand.superclass.should == Imperator::Command }
99
+ end
100
+
101
+ describe '.rest_command' do
102
+ before :all do
103
+ subject.rest_command :update, Payment
104
+ end
105
+
106
+ specify { UpdatePaymentCommand.superclass.should == Imperator::Command::Rest }
107
+
108
+ describe ':all' do
109
+ before :all do
110
+ subject.rest_command :all, Foo
111
+ end
112
+
113
+ specify { CreateFooCommand.superclass.should == Imperator::Command::Rest }
114
+ specify { UpdateFooCommand.superclass.should == Imperator::Command::Rest }
115
+ specify { DeleteFooCommand.superclass.should == Imperator::Command::Rest }
116
+ end
117
+ end
118
+
119
+ describe '.default_rest_class' do
120
+ describe 'default class' do
121
+ its(:default_rest_class) { should == Imperator::Command::Rest }
122
+ end
123
+
124
+ describe 'set default_rest_class to UpdateRestCommand' do
125
+ before :all do
126
+ subject.default_rest_class = UpdateRestCommand
127
+ end
128
+
129
+ its(:default_rest_class) { should == UpdateRestCommand }
130
+ end
131
+ end
132
+
133
+ describe '.default_options' do
134
+ before do
135
+ subject.default_options = {hi: 'hello' }
136
+ end
137
+
138
+ its(:default_options) { should == {hi: 'hello' } }
139
+ end
140
+ end
141
+
142
+ describe 'private methods' do
143
+ describe Imperator::Command::ClassFactory do
144
+ subject { Imperator::Command::ClassFactory }
145
+
146
+ describe 'rest command builders' do
147
+ describe '.create_command_for' do
148
+ before do
149
+ subject.reset_rest_class
150
+ subject.send :create_command_for, Post
151
+ end
152
+
153
+ specify { CreatePostCommand.superclass.should == Imperator::Command::Rest }
154
+ end
155
+
156
+ describe '.update_command_for' do
157
+ before do
158
+ subject.default_rest_class = UpdateRestCommand
159
+ subject.send :update_command_for, Account
160
+ end
161
+
162
+ specify { UpdateAccountCommand.superclass.should == UpdateRestCommand }
163
+ end
164
+
165
+ describe '.delete_command_for' do
166
+ before do
167
+ subject.reset_rest_class
168
+ subject.send :delete_command_for, Account
169
+ end
170
+
171
+ specify { DeleteAccountCommand.superclass.should == Imperator::Command::Rest }
172
+ end
173
+
174
+ describe '.rest_commands_for' do
175
+ before :all do
176
+ subject.send :rest_commands_for, User
177
+ end
178
+
179
+ specify { CreateUserCommand.superclass.should == Imperator::Command::Rest }
180
+ specify { UpdateUserCommand.superclass.should == Imperator::Command::Rest }
181
+ specify { DeleteUserCommand.superclass.should == Imperator::Command::Rest }
182
+ end
183
+ end
184
+ end
185
+ end
186
+
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'imperator/command/macros'
3
+
4
+ class Message
5
+ end
6
+
7
+
8
+ describe 'Imperator command macros' do
9
+ describe '.imperator_class_factory' do
10
+ specify { imperator_class_factory.should == Imperator::Command::ClassFactory }
11
+ end
12
+
13
+ describe '.build_command' do
14
+ before do
15
+ build_command :update, Message
16
+ end
17
+
18
+ specify do
19
+ UpdateMessageCommand.superclass.should == Imperator::Command
20
+ end
21
+ end
22
+
23
+ describe '.build_rest_command' do
24
+ before do
25
+ build_rest_command :delete, Message
26
+ end
27
+
28
+ specify do
29
+ DeleteMessageCommand.superclass.should == Imperator::Command::Rest
30
+ end
31
+ end
32
+ end