brainstem 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,252 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brainstem::Presenter do
4
+ describe "class methods" do
5
+
6
+ describe "presents method" do
7
+ before do
8
+ @klass = Class.new(Brainstem::Presenter)
9
+ end
10
+
11
+ it "records itself as the presenter for the named class as a string" do
12
+ @klass.presents "String"
13
+ Brainstem.presenter_collection.for(String).should be_a(@klass)
14
+ end
15
+
16
+ it "records itself as the presenter for the given class" do
17
+ @klass.presents String
18
+ Brainstem.presenter_collection.for(String).should be_a(@klass)
19
+ end
20
+
21
+ it "records itself as the presenter for the named classes" do
22
+ @klass.presents String, Array
23
+ Brainstem.presenter_collection.for(String).should be_a(@klass)
24
+ Brainstem.presenter_collection.for(Array).should be_a(@klass)
25
+ end
26
+ end
27
+
28
+ describe "implicit namespacing" do
29
+ module V1
30
+ class SomePresenter < Brainstem::Presenter
31
+ end
32
+ end
33
+
34
+ it "uses the closest module name as the presenter namespace" do
35
+ V1::SomePresenter.presents String
36
+ Brainstem.presenter_collection(:v1).for(String).should be_a(V1::SomePresenter)
37
+ end
38
+
39
+ it "does not map namespaced presenters into the default namespace" do
40
+ V1::SomePresenter.presents String
41
+ Brainstem.presenter_collection.for(String).should be_nil
42
+ end
43
+ end
44
+
45
+ describe "helper method" do
46
+ before do
47
+ @klass = Class.new(Brainstem::Presenter) do
48
+ def call_helper
49
+ foo
50
+ end
51
+ end
52
+ @helper = Module.new do
53
+ def foo
54
+ "I work"
55
+ end
56
+ end
57
+ end
58
+
59
+ it "includes and extends the given module" do
60
+ lambda { @klass.new.call_helper }.should raise_error
61
+ @klass.helper @helper
62
+ @klass.new.call_helper.should == "I work"
63
+ @klass.foo.should == "I work"
64
+ end
65
+ end
66
+
67
+ describe "filter method" do
68
+ before do
69
+ @klass = Class.new(Brainstem::Presenter)
70
+ end
71
+
72
+ it "creates an entry in the filters class ivar" do
73
+ @klass.filter(:foo, :default => true) { 1 }
74
+ @klass.filters[:foo][0].should eq({:default => true})
75
+ @klass.filters[:foo][1].should be_a(Proc)
76
+ end
77
+
78
+ it "accepts names without blocks" do
79
+ @klass.filter(:foo)
80
+ @klass.filters[:foo][1].should be_nil
81
+ end
82
+ end
83
+
84
+ describe "search method" do
85
+ before do
86
+ @klass = Class.new(Brainstem::Presenter)
87
+ end
88
+
89
+ it "creates an entry in the search class ivar" do
90
+ @klass.search do end
91
+ @klass.search_block.should be_a(Proc)
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "post_process hooks" do
97
+ describe "adding object ids as strings" do
98
+ before do
99
+ post_presenter = Class.new(Brainstem::Presenter) do
100
+ presents Post
101
+
102
+ def present(model)
103
+ {
104
+ :body => model.body,
105
+ }
106
+ end
107
+ end
108
+
109
+ @presenter = post_presenter.new
110
+ @post = Post.first
111
+ end
112
+
113
+ it "outputs the associated object's id and type" do
114
+ data = @presenter.present_and_post_process(@post)
115
+ data[:id].should eq(@post.id.to_s)
116
+ data[:body].should eq(@post.body)
117
+ end
118
+ end
119
+
120
+ describe "converting dates and times" do
121
+ it "should convert all Time-and-date-like objects to iso8601" do
122
+ class TimePresenter < Brainstem::Presenter
123
+ def present(model)
124
+ {
125
+ :time => Time.now,
126
+ :date => Date.new,
127
+ :recursion => {
128
+ :time => Time.now,
129
+ :something => [Time.now, :else],
130
+ :foo => :bar
131
+ }
132
+ }
133
+ end
134
+ end
135
+
136
+ iso8601_time = /\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}[-+]\d{2}:\d{2}/
137
+ iso8601_date = /\d{4}-\d{2}-\d{2}/
138
+
139
+ struct = TimePresenter.new.present_and_post_process("something")
140
+ struct[:time].should =~ iso8601_time
141
+ struct[:date].should =~ iso8601_date
142
+ struct[:recursion][:time].should =~ iso8601_time
143
+ struct[:recursion][:something].first.should =~ iso8601_time
144
+ struct[:recursion][:something].last.should == :else
145
+ struct[:recursion][:foo].should == :bar
146
+ end
147
+ end
148
+
149
+ describe "outputting polymorphic associations" do
150
+ before do
151
+ some_presenter = Class.new(Brainstem::Presenter) do
152
+ presents Post
153
+
154
+ def present(model)
155
+ {
156
+ :body => model.body,
157
+ :subject => association(:subject),
158
+ :another_subject => association(:subject)
159
+ }
160
+ end
161
+ end
162
+
163
+ @presenter = some_presenter.new
164
+ @post = Post.first
165
+ end
166
+
167
+ it "outputs the associated object's id and type" do
168
+ data = @presenter.present_and_post_process(@post)
169
+ data[:subject_id].should eq(@post.subject_id.to_s)
170
+ data[:subject_type].should eq(@post.subject_type)
171
+ end
172
+
173
+ it "outputs custom names for an associated object's id and type" do
174
+ data = @presenter.present_and_post_process(@post)
175
+ data[:another_subject_id].should eq(@post.subject_id.to_s)
176
+ data[:another_subject_type].should eq(@post.subject_type)
177
+ end
178
+ end
179
+
180
+ describe "outputting associations" do
181
+ before do
182
+ some_presenter = Class.new(Brainstem::Presenter) do
183
+ presents Workspace
184
+
185
+ def present(model)
186
+ {
187
+ :updated_at => model.updated_at,
188
+ :tasks => association(:tasks),
189
+ :user => association(:user),
190
+ :something => association(:user),
191
+ :lead_user => association(:lead_user),
192
+ :lead_user_with_lambda => association { model.user },
193
+ :synthetic => association(:synthetic)
194
+ }
195
+ end
196
+ end
197
+
198
+ @presenter = some_presenter.new
199
+ @workspace = Workspace.find_by_title "bob workspace 1"
200
+ end
201
+
202
+ it "should not convert or return non-included associations, but should return <association>_id for belongs_to relationships, plus all fields" do
203
+ json = @presenter.present_and_post_process(@workspace, [])
204
+ json.keys.should =~ [:id, :updated_at, :something_id, :user_id]
205
+ end
206
+
207
+ it "should convert requested has_many associations (includes) into the <association>_ids format" do
208
+ @workspace.tasks.length.should > 0
209
+ @presenter.present_and_post_process(@workspace, [:tasks])[:task_ids].should =~ @workspace.tasks.map(&:id).map(&:to_s)
210
+ end
211
+
212
+ it "should convert requested belongs_to and has_one associations into the <association>_id format when requested" do
213
+ @presenter.present_and_post_process(@workspace, [:user])[:user_id].should == @workspace.user.id.to_s
214
+ end
215
+
216
+ it "converts non-association models into <model>_id format when they are requested" do
217
+ @presenter.present_and_post_process(@workspace, [:lead_user])[:lead_user_id].should == @workspace.lead_user.id.to_s
218
+ end
219
+
220
+ it "handles associations provided with lambdas" do
221
+ @presenter.present_and_post_process(@workspace, [:lead_user_with_lambda])[:lead_user_with_lambda_id].should == @workspace.lead_user.id.to_s
222
+ end
223
+
224
+ it "should return <association>_id fields when the given association ids exist on the model whether it is requested or not" do
225
+ @presenter.present_and_post_process(@workspace, [:user])[:user_id].should == @workspace.user_id.to_s
226
+
227
+ json = @presenter.present_and_post_process(@workspace, [])
228
+ json.keys.should =~ [:user_id, :something_id, :id, :updated_at]
229
+ json[:user_id].should == @workspace.user_id.to_s
230
+ json[:something_id].should == @workspace.user_id.to_s
231
+ end
232
+
233
+ it "should return null, not empty string when ids are missing" do
234
+ @workspace.user = nil
235
+ @workspace.tasks = []
236
+ @presenter.present_and_post_process(@workspace, [:lead_user_with_lambda])[:lead_user_with_lambda_id].should == nil
237
+ @presenter.present_and_post_process(@workspace, [:user])[:user_id].should == nil
238
+ @presenter.present_and_post_process(@workspace, [:something])[:something_id].should == nil
239
+ @presenter.present_and_post_process(@workspace, [:tasks])[:task_ids].should == []
240
+ end
241
+
242
+ context "when the model has an <association>_id method but no column" do
243
+ it "does not include the <association>_id field" do
244
+ def @workspace.synthetic_id
245
+ raise "this explodes because it's not an association"
246
+ end
247
+ @presenter.present_and_post_process(@workspace, []).should_not have_key(:synthetic_id)
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brainstem do
4
+ describe "default_namespace attribute" do
5
+ it "can be set and read" do
6
+ Brainstem.default_namespace = "something"
7
+ Brainstem.default_namespace.should eq("something")
8
+ end
9
+
10
+ it "returns 'none' if unset" do
11
+ Brainstem.default_namespace.should eq("none")
12
+ end
13
+ end
14
+
15
+ describe "presenter collection method" do
16
+ it "returns an instance of PresenterCollection" do
17
+ Brainstem.presenter_collection.should be_a(Brainstem::PresenterCollection)
18
+ end
19
+
20
+ it "accepts a namespace" do
21
+ Brainstem.presenter_collection("v1").should be_a(Brainstem::PresenterCollection)
22
+ Brainstem.presenter_collection("v1").should_not eq(Brainstem.presenter_collection)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'active_record'
2
+ require 'logger'
3
+ require 'rr'
4
+ require 'rspec'
5
+ require 'sqlite3'
6
+
7
+ require 'brainstem'
8
+ require_relative 'spec_helpers/db'
9
+ require_relative 'spec_helpers/cleanup'
10
+
11
+ RSpec.configure do |config|
12
+ config.mock_with :rr
13
+
14
+ config.before(:each) do
15
+ Brainstem.logger = Logger.new(StringIO.new)
16
+ end
17
+
18
+ config.after(:each) do
19
+ Brainstem.clear_collections!
20
+ Brainstem.default_namespace = nil
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module Brainstem
2
+
3
+ def self.clear_collections!
4
+ presenter_collection.presenters.each do |klass, presenter|
5
+ presenter.clear_options!
6
+ end
7
+ @presenter_collection = {}
8
+ end
9
+
10
+ class Presenter
11
+ def self.clear_options!
12
+ @default_sort_order = nil
13
+ @filters = nil
14
+ @sort_orders = nil
15
+ @search_block = nil
16
+ end
17
+
18
+ def clear_options!
19
+ self.class.clear_options!
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,79 @@
1
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
2
+ ActiveRecord::Schema.define do
3
+ self.verbose = false
4
+
5
+ create_table :users, :force => true do |t|
6
+ t.string :username
7
+ t.timestamps
8
+ end
9
+
10
+ create_table :workspaces, :force => true do |t|
11
+ t.string :title
12
+ t.string :description
13
+ t.belongs_to :user
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :tasks, :force => true do |t|
18
+ t.string :name
19
+ t.integer :parent_id
20
+ t.belongs_to :workspace
21
+ t.timestamps
22
+ end
23
+
24
+ create_table :posts, :force => true do |t|
25
+ t.string :body
26
+ t.integer :subject_id
27
+ t.string :subject_type
28
+ t.timestamps
29
+ end
30
+ end
31
+
32
+ class User < ActiveRecord::Base
33
+ has_many :workspaces
34
+ end
35
+
36
+ class Task < ActiveRecord::Base
37
+ belongs_to :workspace
38
+ has_many :sub_tasks, :foreign_key => :parent_id, :class_name => "Task"
39
+ has_many :posts
40
+
41
+ def tags
42
+ %w[some tags]
43
+ end
44
+ end
45
+
46
+ class Workspace < ActiveRecord::Base
47
+ belongs_to :user
48
+ has_many :tasks
49
+ has_many :posts
50
+
51
+ scope :owned_by, -> id { where(:user_id => id) }
52
+ scope :numeric_description, where(:description => ["1", "2", "3"])
53
+
54
+ def lead_user
55
+ user
56
+ end
57
+ end
58
+
59
+ class Post < ActiveRecord::Base
60
+ belongs_to :subject, :polymorphic => true
61
+ end
62
+
63
+ User.create!(:id => 1, :username => "bob")
64
+ User.create!(:id => 2, :username => "jane")
65
+
66
+ Workspace.create!(:id => 1, :user_id => 1, :title => "bob workspace 1", :description => "a")
67
+ Workspace.create!(:id => 2, :user_id => 1, :title => "bob workspace 2", :description => "1")
68
+ Workspace.create!(:id => 3, :user_id => 1, :title => "bob workspace 3", :description => "b")
69
+ Workspace.create!(:id => 4, :user_id => 1, :title => "bob workspace 4", :description => "2")
70
+ Workspace.create!(:id => 5, :user_id => 2, :title => "jane workspace 1", :description => "c")
71
+ Workspace.create!(:id => 6, :user_id => 2, :title => "jane workspace 2", :description => "3")
72
+
73
+ Task.create!(:id => 1, :workspace_id => 1, :name => "Buy milk")
74
+ Task.create!(:id => 2, :workspace_id => 1, :name => "Buy bananas")
75
+ Task.create!(:id => 3, :workspace_id => 1, :parent_id => 2, :name => "Green preferred")
76
+ Task.create!(:id => 4, :workspace_id => 1, :parent_id => 2, :name => "One bunch")
77
+
78
+ Post.create!(:id => 1, :subject => Workspace.first, :body => "first post!")
79
+ Post.create!(:id => 2, :subject => Task.first, :body => "this is important. get on it!")
@@ -0,0 +1,39 @@
1
+ class WorkspacePresenter < Brainstem::Presenter
2
+ def present(model)
3
+ {
4
+ :title => model.title,
5
+ :description => model.description,
6
+ :updated_at => model.updated_at,
7
+ :tasks => association(:tasks),
8
+ :lead_user => association(:lead_user, :json_name => "users")
9
+ }
10
+ end
11
+ end
12
+
13
+ class TaskPresenter < Brainstem::Presenter
14
+ def present(model)
15
+ {
16
+ :name => model.name,
17
+ :sub_tasks => association(:sub_tasks),
18
+ :other_tasks => association(:sub_tasks, :json_name => "other_tasks"),
19
+ :workspace => association(:workspace)
20
+ }
21
+ end
22
+ end
23
+
24
+ class UserPresenter < Brainstem::Presenter
25
+ def present(model)
26
+ {
27
+ :username => model.username
28
+ }
29
+ end
30
+ end
31
+
32
+ class PostPresenter < Brainstem::Presenter
33
+ def present(model)
34
+ {
35
+ :body => model.body,
36
+ :subject => association(:subject)
37
+ }
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainstem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sufyan Adam
9
+ - André Arko
10
+ - Andrew Cantino
11
+ - Katlyn Daniluk
12
+ - Reid Gillette
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2013-04-29 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: activerecord
20
+ requirement: !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ requirement: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ - !ruby/object:Gem::Dependency
51
+ name: redcarpet
52
+ requirement: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ type: :development
59
+ prerelease: false
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: rr
68
+ requirement: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: sqlite3
100
+ requirement: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ - !ruby/object:Gem::Dependency
115
+ name: yard
116
+ requirement: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ description: Brainstem allows you to create rich API presenters that know how to filter,
131
+ sort, and include associations.
132
+ email:
133
+ - dev@mavenlink.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - brainstem.gemspec
139
+ - Gemfile
140
+ - Gemfile.lock
141
+ - Guardfile
142
+ - lib/brainstem/association_field.rb
143
+ - lib/brainstem/controller_methods.rb
144
+ - lib/brainstem/engine.rb
145
+ - lib/brainstem/presenter.rb
146
+ - lib/brainstem/presenter_collection.rb
147
+ - lib/brainstem/time_classes.rb
148
+ - lib/brainstem/version.rb
149
+ - lib/brainstem.rb
150
+ - LICENSE
151
+ - Rakefile
152
+ - README.md
153
+ - spec/brainstem/controller_methods_spec.rb
154
+ - spec/brainstem/presenter_collection_spec.rb
155
+ - spec/brainstem/presenter_spec.rb
156
+ - spec/brainstem_spec.rb
157
+ - spec/spec_helper.rb
158
+ - spec/spec_helpers/cleanup.rb
159
+ - spec/spec_helpers/db.rb
160
+ - spec/spec_helpers/presenters.rb
161
+ homepage: http://developer.mavenlink.com
162
+ licenses:
163
+ - MIT
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ segments:
175
+ - 0
176
+ hash: -2131973950543043950
177
+ required_rubygems_version: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ segments:
184
+ - 0
185
+ hash: -2131973950543043950
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 1.8.25
189
+ signing_key:
190
+ specification_version: 3
191
+ summary: ActiveRecord presenters with a rich request API
192
+ test_files:
193
+ - spec/brainstem/controller_methods_spec.rb
194
+ - spec/brainstem/presenter_collection_spec.rb
195
+ - spec/brainstem/presenter_spec.rb
196
+ - spec/brainstem_spec.rb
197
+ - spec/spec_helper.rb
198
+ - spec/spec_helpers/cleanup.rb
199
+ - spec/spec_helpers/db.rb
200
+ - spec/spec_helpers/presenters.rb
201
+ has_rdoc: