roar-extensions 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ module RoarExtensions
2
+ module ResourceLinks
3
+ private
4
+ def merge_links(collection, &presenter_generator)
5
+ collection.inject({}) do |acc, element|
6
+ acc.merge(presenter_generator.call(element).to_hash)
7
+ end
8
+ end
9
+
10
+ def resource_link_json(link_hash)
11
+ link_hash.inject({}) do |acc, (rel, href)|
12
+ acc.merge(LinkPresenter.new(rel, href).to_hash)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module RoarExtensions
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/roar_extensions/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Donald Plummer", "Michael Xavier"]
6
+ gem.email = ["developers@crystalcommerce.com"]
7
+ gem.description = %q{Useful extensions to roar}
8
+ gem.summary = %q{Useful extensions to roar}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "roar-extensions"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = RoarExtensions::VERSION
17
+
18
+ gem.add_dependency("roar", "0.11.3")
19
+ gem.add_dependency("activesupport", ">= 2.3.14")
20
+
21
+ gem.add_development_dependency "pry"
22
+ gem.add_development_dependency "pry-nav"
23
+ gem.add_development_dependency "rake", "~>0.9.2"
24
+ gem.add_development_dependency "rspec", "~>2.10.0"
25
+ gem.add_development_dependency "guard", "~>1.2.1"
26
+ gem.add_development_dependency "guard-rspec", "~>1.1.0"
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe DestroyedRecordPresenter do
6
+ class DestroyedRecordPresenterTest
7
+ include DestroyedRecordPresenter
8
+ root_element :foo
9
+ end
10
+
11
+ subject { DestroyedRecordPresenterTest.new(123) }
12
+
13
+ it "has just the id" do
14
+ subject.to_hash.should == {'foo' => {'id' => 123}}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions::Helpers
5
+ describe EmbeddedParameterParsing do
6
+ class MyTestController
7
+ def self.before_filter(*args)
8
+ # yup
9
+ end
10
+ end
11
+
12
+ subject { MyTestController.new }
13
+
14
+ describe "including into a class" do
15
+ it "calls before_filter" do
16
+ MyTestController.should_receive(:before_filter).with(:parse_embedded_params_filter)
17
+ MyTestController.send(:include, EmbeddedParameterParsing)
18
+ end
19
+ end
20
+
21
+ describe "parse_embedded_params" do
22
+ before(:each) do
23
+ MyTestController.send(:include, EmbeddedParameterParsing)
24
+ end
25
+ it "returns empty array for blank string" do
26
+ subject.parse_embedded_params("").should == []
27
+ end
28
+
29
+ it "returns empty array for nil" do
30
+ subject.parse_embedded_params(nil).should == []
31
+ end
32
+
33
+ it "returns the given list if comma-separated as symbols" do
34
+ subject.parse_embedded_params("foo,bar").should == [:foo, :bar]
35
+ end
36
+
37
+ it "recursively parses nested embeddings" do
38
+ subject.parse_embedded_params("line_items:product,line_items:variant,customer,address:ip_address").should == [
39
+ :customer,
40
+ {:line_items => [:product, :variant], :address => [:ip_address]}
41
+ ]
42
+ end
43
+
44
+ it "recurses multiple levels" do
45
+ subject.parse_embedded_params("customer,line_items:product:category").should == [
46
+ :customer,
47
+ {:line_items => [{:product => [:category]}]}
48
+ ]
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe LinkPresenter do
6
+ subject { LinkPresenter.new('search_engine', 'http://google.com') }
7
+
8
+ describe "#==" do
9
+ it "returns true if href, rel, and title match" do
10
+ subject.should == LinkPresenter.new('search_engine', 'http://google.com')
11
+ end
12
+
13
+ it "returns false if href, rel, or title differ" do
14
+ subject.should_not == LinkPresenter.new('search_engine', 'http://bing.com')
15
+ subject.should_not == LinkPresenter.new('weee', 'http://google.com')
16
+ subject.should_not == LinkPresenter.new('search_engine', 'http://google.com', 'cows')
17
+ end
18
+ end
19
+
20
+ context "no title" do
21
+ its(:as_json) do
22
+ should == { 'search_engine' => {:href => 'http://google.com'} }
23
+ end
24
+
25
+ it "aliases to_hash to as_json" do
26
+ subject.to_hash.should == subject.as_json
27
+ end
28
+ end
29
+
30
+ context "title given" do
31
+ subject { LinkPresenter.new('search_engine',
32
+ 'http://google.com',
33
+ 'Cool Search') }
34
+ its(:as_json) do
35
+ should == {
36
+ 'search_engine' => {
37
+ :href => 'http://google.com',
38
+ :title => 'Cool Search'
39
+ }
40
+ }
41
+ end
42
+
43
+ it "aliases to_hash to as_json" do
44
+ subject.to_hash.should == subject.as_json
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe MoneyPresenter do
6
+ let(:currency) { stub(:iso_code => "USD") }
7
+ let(:money) { stub(:currency => currency, :cents => 100) }
8
+
9
+ subject { MoneyPresenter.new(money) }
10
+
11
+ it "has cents" do
12
+ subject.to_hash['money']['cents'].should == 100
13
+ end
14
+
15
+ it "has currency" do
16
+ subject.to_hash['money']['currency'].should == "USD"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+ require "roar_extensions"
3
+
4
+ module RoarExtensions
5
+ describe PaginatedCollectionPresenter do
6
+ class TestEntry
7
+ include RoarExtensions::Presenter
8
+
9
+ attr_reader :name, :age
10
+
11
+ def initialize(name, age)
12
+ @name = name
13
+ @age = age
14
+ end
15
+
16
+ property :name
17
+ property :age
18
+ property :lowercased_name, :from => :name_downcase
19
+
20
+ def name_downcase
21
+ name.downcase
22
+ end
23
+
24
+ end
25
+
26
+ let(:current_page) { 1 }
27
+ let(:next_page) { 2 }
28
+ let(:previous_page) { nil }
29
+ let(:entries) {[
30
+ TestEntry.new("Bob", 41)
31
+ ]}
32
+ let(:paginated_result) do
33
+ mock("Paginated Result", :total_pages => 3,
34
+ :current_page => current_page,
35
+ :next_page => next_page,
36
+ :previous_page => previous_page,
37
+ :total_entries => 3,
38
+ :collect => entries,
39
+ :per_page => 1)
40
+ end
41
+ let(:base_path) { "/things" }
42
+ let(:json_options) {{}}
43
+
44
+ describe "#to_hash" do
45
+ let(:presenter) { PaginatedCollectionPresenter.new(paginated_result,
46
+ base_path)}
47
+ subject { JSON.parse(presenter.to_json(json_options))['paginated_collection'] }
48
+
49
+ it "includes the total pages" do
50
+ subject['total_pages'].should == 3
51
+ end
52
+
53
+ it "includes the total entries" do
54
+ subject['total_entries'].should == 3
55
+ end
56
+
57
+ it "includes the per page" do
58
+ subject['per_page'].should == 1
59
+ end
60
+
61
+ it "includes the self link" do
62
+ subject['_links']['self'].should == {"href" => "/things"}
63
+ end
64
+
65
+ it "includes the next_page link" do
66
+ subject['_links']['next_page'].should == {"href" => "/things?page=2"}
67
+ end
68
+
69
+ it "does not include the previous_page link on the first page" do
70
+ subject['_links'].should_not have_key('previous_page')
71
+ end
72
+
73
+ it "includes the paginated results under the entries key" do
74
+ subject['entries'].should == [{'name' => 'Bob',
75
+ 'age' => 41,
76
+ 'lowercased_name' => 'bob'}]
77
+ end
78
+
79
+ it "includes current_page" do
80
+ subject["current_page"].should == 1
81
+ end
82
+
83
+ it "includes next_page" do
84
+ subject["next_page"].should == 2
85
+ end
86
+
87
+ it "includes previous_page" do
88
+ subject["previous_page"].should == nil
89
+ end
90
+
91
+ context "on last page" do
92
+ let(:previous_page) { 2 }
93
+ let(:current_page) { 3 }
94
+ let(:next_page) { nil }
95
+
96
+ it "includes the self link" do
97
+ subject['_links']['self'].should == {"href" => "/things?page=3"}
98
+ end
99
+
100
+ it "does not include the next_page link" do
101
+ subject['_links'].should_not have_key('next_page')
102
+ end
103
+
104
+ it "includes the previous_page link" do
105
+ subject['_links']['previous_page'].should == {"href" => "/things?page=2"}
106
+ end
107
+
108
+ it "includes current_page" do
109
+ subject["current_page"].should == 3
110
+ end
111
+
112
+ it "includes next_page" do
113
+ subject["next_page"].should == nil
114
+ end
115
+
116
+ it "includes previous_page" do
117
+ subject["previous_page"].should == 2
118
+ end
119
+ end
120
+
121
+ context "middle page" do
122
+ let(:previous_page) { 1 }
123
+ let(:current_page) { 2 }
124
+ let(:next_page) { 3 }
125
+
126
+ it "includes the self link" do
127
+ subject['_links']['self'].should == {"href" => "/things?page=2"}
128
+ end
129
+
130
+ it "includes the next_page link" do
131
+ subject['_links']['next_page'].should == {"href" => "/things?page=3"}
132
+ end
133
+
134
+ it "includes the previous_page link" do
135
+ subject['_links']['previous_page'].should == {"href" => "/things"}
136
+ end
137
+
138
+ it "includes current_page" do
139
+ subject["current_page"].should == 2
140
+ end
141
+
142
+ it "includes next_page" do
143
+ subject["next_page"].should == 3
144
+ end
145
+
146
+ it "includes previous_page" do
147
+ subject["previous_page"].should == 1
148
+ end
149
+ end
150
+
151
+ context "attribute whitelisting" do
152
+ let(:json_options) {{:include => [:name]}}
153
+
154
+ it "the include option is passed to the entries" do
155
+ subject['entries'].should == [{'name' => 'Bob'}]
156
+ end
157
+
158
+ context "using the :from property option" do
159
+ let(:json_options) {{:include => ["lowercased_name"]}}
160
+
161
+ it "the include option is passed to the entries" do
162
+ subject['entries'].should == [{'lowercased_name' => 'bob'}]
163
+ end
164
+ end
165
+ end
166
+
167
+ context "attribute blacklisting" do
168
+ let(:json_options) {{:exclude => [:name]}}
169
+
170
+ it "the include option is passed to the entries" do
171
+ subject['entries'].should == [{'age' => 41, 'lowercased_name' => 'bob'}]
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe Presenter do
6
+
7
+ class TestPhotoPresenter
8
+ include Presenter
9
+
10
+ root_element :photo
11
+
12
+ delegated_property :position
13
+ end
14
+
15
+ class TestProductPresenter
16
+ include Presenter
17
+
18
+ root_element :product
19
+
20
+ delegated_property :id, :always_include => true
21
+ delegated_property :name
22
+ delegated_property :is_buying, :from => :buying?
23
+ delegated_property :msrp, :as => MoneyPresenter
24
+ property :possible_variants_count
25
+ property :min_sell_price, :as => MoneyPresenter
26
+ property :catalog_links, :from => :catalog_links_as_json
27
+ delegated_collection :photos, :as => TestPhotoPresenter, :embedded => true
28
+
29
+ link(:rel => "self") { "/v1/products/#{record.id}" }
30
+ link(:rel => "category") { "/v1/categories/#{record.category_id}" }
31
+ link(:rel => "related_products") { "/v1/products/#{record.id}/related" }
32
+
33
+ def initialize(record, options = {})
34
+ super(record)
35
+
36
+ @embedded = options.fetch(:embedded, [])
37
+ end
38
+
39
+ private
40
+
41
+ def min_sell_price
42
+ OpenStruct.new(:cents => 499,
43
+ :currency => OpenStruct.new(:iso_code => 'USD'))
44
+ end
45
+
46
+ def possible_variants_count
47
+ 3
48
+ end
49
+
50
+ def catalog_links_as_json
51
+ {'omg' => 'wtf'}
52
+ end
53
+ end
54
+
55
+ let(:product) {
56
+ mock("Product Record",
57
+ :id => 9001,
58
+ :category_id => 800,
59
+ :name => "Worship",
60
+ :photos => [photo],
61
+ :msrp => stub(:cents => 300, :currency => stub(:iso_code => 'USD')),
62
+ :buying? => true)
63
+ }
64
+
65
+ let(:photo) {
66
+ mock("Photo", :position => 1)
67
+ }
68
+
69
+ let(:options) { {} }
70
+ let(:json_options) { {} }
71
+
72
+ let(:presenter) { TestProductPresenter.new(product, options) }
73
+
74
+ subject { presenter }
75
+
76
+ it "aliases to_hash to as_json" do
77
+ subject.to_hash.should == subject.as_json
78
+ end
79
+
80
+ context "as_json" do
81
+ subject { presenter.as_json(json_options)['product'] }
82
+
83
+ it "has a name" do
84
+ subject['name'].should == 'Worship'
85
+ end
86
+
87
+ it "has a possible_variants_count" do
88
+ subject['possible_variants_count'].should == 3
89
+ end
90
+
91
+ it "has a catalog_links" do
92
+ subject['catalog_links'].should == {'omg' => 'wtf'}
93
+ end
94
+
95
+ it "has an msrp as money" do
96
+ subject['msrp'].should == {
97
+ 'money' => {
98
+ 'cents' => 300,
99
+ 'currency' => 'USD'
100
+ }
101
+ }
102
+ end
103
+
104
+ it "has a min sell price as money" do
105
+ subject['min_sell_price'].should == {
106
+ 'money' => {
107
+ 'cents' => 499,
108
+ 'currency' => 'USD'
109
+ }
110
+ }
111
+ end
112
+
113
+ it "has an id" do
114
+ subject['id'].should == 9001
115
+ end
116
+
117
+ it "has the buyingness" do
118
+ subject['is_buying'].should == true
119
+ end
120
+
121
+
122
+ it "has api _links" do
123
+ subject['_links'].should == {
124
+ 'self' => { :href => "/v1/products/9001" },
125
+ 'related_products' => { :href => "/v1/products/9001/related" },
126
+ 'category' => { :href => '/v1/categories/800'}
127
+ }
128
+ end
129
+
130
+ it "does not embed" do
131
+ subject.should_not have_key('_embedded')
132
+ end
133
+
134
+ context "embedding of photos enabled" do
135
+ let(:options) { {:embedded => [:photos]} }
136
+
137
+ it "has embedded photos" do
138
+ subject['_embedded']['photos'].should == [
139
+ {
140
+ 'photo' => {
141
+ 'position' => 1
142
+ }
143
+ }
144
+ ]
145
+ end
146
+ end
147
+
148
+ context "always renders nil attributes" do
149
+ before(:each) do
150
+ product.stub(:name).and_return(nil)
151
+ end
152
+
153
+ it "has a null name" do
154
+ subject.fetch('name').should == nil
155
+ end
156
+ end
157
+
158
+ context "limiting attributes returned" do
159
+ let(:json_options) {{ :include => [:name] }}
160
+
161
+ it "limits to the attributes requested, plus required attributes" do
162
+ subject.keys.should == ['id', 'name']
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end