her 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +2 -2
  3. data/.rspec +1 -2
  4. data/.travis.yml +2 -2
  5. data/README.md +10 -16
  6. data/UPGRADE.md +4 -0
  7. data/examples/grape-and-her/.env.default +3 -0
  8. data/examples/grape-and-her/Procfile +2 -0
  9. data/examples/grape-and-her/README.md +27 -0
  10. data/examples/grape-and-her/api/Gemfile +11 -0
  11. data/examples/grape-and-her/api/Rakefile +14 -0
  12. data/examples/grape-and-her/api/app/api.rb +49 -0
  13. data/examples/grape-and-her/api/app/models/organization.rb +7 -0
  14. data/examples/grape-and-her/api/app/models/user.rb +9 -0
  15. data/examples/grape-and-her/api/app/views/organizations/_base.rabl +2 -0
  16. data/examples/grape-and-her/api/app/views/organizations/index.rabl +3 -0
  17. data/examples/grape-and-her/api/app/views/organizations/show.rabl +3 -0
  18. data/examples/grape-and-her/api/app/views/users/_base.rabl +8 -0
  19. data/examples/grape-and-her/api/app/views/users/index.rabl +3 -0
  20. data/examples/grape-and-her/api/app/views/users/show.rabl +3 -0
  21. data/examples/grape-and-her/api/config.ru +5 -0
  22. data/examples/grape-and-her/api/config/boot.rb +17 -0
  23. data/examples/grape-and-her/api/config/unicorn.rb +7 -0
  24. data/examples/grape-and-her/api/db/migrations/001_create_users.rb +11 -0
  25. data/examples/grape-and-her/api/db/migrations/002_create_organizations.rb +8 -0
  26. data/examples/grape-and-her/consumer/Gemfile +23 -0
  27. data/examples/grape-and-her/consumer/app/assets/stylesheets/application.scss +190 -0
  28. data/examples/grape-and-her/consumer/app/assets/stylesheets/reset.scss +53 -0
  29. data/examples/grape-and-her/consumer/app/consumer.rb +74 -0
  30. data/examples/grape-and-her/consumer/app/models/organization.rb +13 -0
  31. data/examples/grape-and-her/consumer/app/models/user.rb +13 -0
  32. data/examples/grape-and-her/consumer/app/views/index.haml +9 -0
  33. data/examples/grape-and-her/consumer/app/views/layout.haml +20 -0
  34. data/examples/grape-and-her/consumer/app/views/organizations/index.haml +25 -0
  35. data/examples/grape-and-her/consumer/app/views/organizations/show.haml +11 -0
  36. data/examples/grape-and-her/consumer/app/views/users/index.haml +33 -0
  37. data/examples/grape-and-her/consumer/app/views/users/show.haml +9 -0
  38. data/examples/grape-and-her/consumer/config.ru +20 -0
  39. data/examples/grape-and-her/consumer/config/boot.rb +30 -0
  40. data/examples/grape-and-her/consumer/config/unicorn.rb +7 -0
  41. data/examples/grape-and-her/consumer/lib/response_logger.rb +18 -0
  42. data/her.gemspec +2 -2
  43. data/lib/her/model.rb +22 -26
  44. data/lib/her/model/associations.rb +19 -19
  45. data/lib/her/model/attributes.rb +173 -0
  46. data/lib/her/model/base.rb +17 -0
  47. data/lib/her/model/http.rb +58 -242
  48. data/lib/her/model/introspection.rb +7 -8
  49. data/lib/her/model/nested_attributes.rb +3 -3
  50. data/lib/her/model/orm.rb +15 -205
  51. data/lib/her/model/parse.rb +86 -0
  52. data/lib/her/model/paths.rb +54 -14
  53. data/lib/her/version.rb +1 -1
  54. data/spec/model/attributes_spec.rb +139 -0
  55. data/spec/model/dirty_spec.rb +40 -0
  56. data/spec/model/introspection_spec.rb +5 -5
  57. data/spec/model/orm_spec.rb +14 -128
  58. data/spec/model/paths_spec.rb +26 -0
  59. data/spec/model/validations_spec.rb +17 -0
  60. data/spec/spec_helper.rb +7 -32
  61. data/spec/support/extensions/array.rb +5 -0
  62. data/spec/support/extensions/hash.rb +5 -0
  63. data/spec/support/macros/model_macros.rb +29 -0
  64. metadata +52 -15
  65. data/examples/twitter-oauth/Gemfile +0 -13
  66. data/examples/twitter-oauth/app.rb +0 -50
  67. data/examples/twitter-oauth/config.ru +0 -5
  68. data/examples/twitter-oauth/views/index.haml +0 -9
  69. data/examples/twitter-search/Gemfile +0 -12
  70. data/examples/twitter-search/app.rb +0 -55
  71. data/examples/twitter-search/config.ru +0 -5
  72. data/examples/twitter-search/views/index.haml +0 -9
data/lib/her/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.5.3"
2
+ VERSION = "0.5.4"
3
3
  end
@@ -0,0 +1,139 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::Attributes do
5
+ context "mapping data to Ruby objects" do
6
+ before { spawn_model "Foo::User" }
7
+
8
+ it "handles new resource" do
9
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke")
10
+ @new_user.new?.should be_true
11
+ @new_user.fullname.should == "Tobias Fünke"
12
+ end
13
+
14
+ it "accepts new resource with strings as hash keys" do
15
+ @new_user = Foo::User.new('fullname' => "Tobias Fünke")
16
+ @new_user.fullname.should == "Tobias Fünke"
17
+ end
18
+
19
+ it "handles method missing for getter" do
20
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
21
+ lambda { @new_user.unknown_method_for_a_user }.should raise_error(NoMethodError)
22
+ expect { @new_user.fullname }.to_not raise_error(NoMethodError)
23
+ end
24
+
25
+ it "handles method missing for setter" do
26
+ @new_user = Foo::User.new
27
+ expect { @new_user.fullname = "Tobias Fünke" }.to_not raise_error(NoMethodError)
28
+ end
29
+
30
+ it "handles method missing for query" do
31
+ @new_user = Foo::User.new
32
+ expect { @new_user.fullname? }.to_not raise_error(NoMethodError)
33
+ end
34
+
35
+ it "handles respond_to for getter" do
36
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
37
+ @new_user.should_not respond_to(:unknown_method_for_a_user)
38
+ @new_user.should respond_to(:fullname)
39
+ end
40
+
41
+ it "handles respond_to for setter" do
42
+ @new_user = Foo::User.new
43
+ @new_user.should respond_to(:fullname=)
44
+ end
45
+
46
+ it "handles respond_to for query" do
47
+ @new_user = Foo::User.new
48
+ @new_user.should respond_to(:fullname?)
49
+ end
50
+
51
+ it "handles has_data? for getter" do
52
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
53
+ @new_user.should_not have_data(:unknown_method_for_a_user)
54
+ @new_user.should have_data(:fullname)
55
+ end
56
+
57
+ it "handles get_data for getter" do
58
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
59
+ @new_user.get_data(:unknown_method_for_a_user).should be_nil
60
+ @new_user.get_data(:fullname).should == 'Mayonegg'
61
+ end
62
+ end
63
+
64
+
65
+ context "assigning new resource data" do
66
+ before do
67
+ spawn_model "Foo::User"
68
+ @user = Foo::User.new(:active => false)
69
+ end
70
+
71
+ it "handles data update through #assign_attributes" do
72
+ @user.assign_attributes :active => true
73
+ @user.should be_active
74
+ end
75
+ end
76
+
77
+ context "checking resource equality" do
78
+ before do
79
+ Her::API.setup :url => "https://api.example.com" do |builder|
80
+ builder.use Her::Middleware::FirstLevelParseJSON
81
+ builder.use Faraday::Request::UrlEncoded
82
+ builder.adapter :test do |stub|
83
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
84
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
85
+ stub.get("/admins/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
86
+ end
87
+ end
88
+
89
+ spawn_model "Foo::User"
90
+ spawn_model "Foo::Admin"
91
+ end
92
+
93
+ let(:user) { Foo::User.find(1) }
94
+
95
+ it "returns true for the exact same object" do
96
+ user.should == user
97
+ end
98
+
99
+ it "returns true for the same resource via find" do
100
+ user.should == Foo::User.find(1)
101
+ end
102
+
103
+ it "returns true for the same class with identical data" do
104
+ user.should == Foo::User.new(:id => 1, :fullname => "Lindsay Fünke")
105
+ end
106
+
107
+ it "returns true for a different resource with the same data" do
108
+ user.should == Foo::Admin.find(1)
109
+ end
110
+
111
+ it "returns false for the same class with different data" do
112
+ user.should_not == Foo::User.new(:id => 2, :fullname => "Tobias Fünke")
113
+ end
114
+
115
+ it "returns false for a non-resource with the same data" do
116
+ fake_user = stub(:data => { :id => 1, :fullname => "Lindsay Fünke" })
117
+ user.should_not == fake_user
118
+ end
119
+
120
+ it "delegates eql? to ==" do
121
+ other = Object.new
122
+ user.should_receive(:==).with(other).and_return(true)
123
+ user.eql?(other).should be_true
124
+ end
125
+
126
+ it "treats equal resources as equal for Array#uniq" do
127
+ user2 = Foo::User.find(1)
128
+ [user, user2].uniq.should == [user]
129
+ end
130
+
131
+ it "treats equal resources as equal for hash keys" do
132
+ Foo::User.find(1)
133
+ hash = { user => true }
134
+ hash[Foo::User.find(1)] = false
135
+ hash.size.should == 1
136
+ hash.should == { user => false }
137
+ end
138
+ end
139
+ end
@@ -2,4 +2,44 @@
2
2
  require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
3
 
4
4
  describe "Her::Model and ActiveModel::Dirty" do
5
+ context "checking dirty attributes" do
6
+ before do
7
+ Her::API.setup :url => "https://api.example.com" do |builder|
8
+ builder.use Her::Middleware::FirstLevelParseJSON
9
+ builder.use Faraday::Request::UrlEncoded
10
+ builder.adapter :test do |stub|
11
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
12
+ stub.put("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
13
+ stub.post("/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
14
+ end
15
+ end
16
+
17
+ spawn_model "Foo::User" do
18
+ attributes :fullname, :email
19
+ end
20
+ end
21
+
22
+ context "for existing resource" do
23
+ it "tracks dirty attributes" do
24
+ user = Foo::User.find(1)
25
+ user.fullname = "Tobias Fünke"
26
+ user.fullname_changed?.should be_true
27
+ user.email_changed?.should be_false
28
+ user.should be_changed
29
+ user.save
30
+ user.should_not be_changed
31
+ end
32
+ end
33
+
34
+ context "for new resource" do
35
+ it "tracks dirty attributes" do
36
+ user = Foo::User.new
37
+ user.fullname = "Tobias Fünke"
38
+ user.fullname_changed?.should be_true
39
+ user.should be_changed
40
+ user.save
41
+ user.should_not be_changed
42
+ end
43
+ end
44
+ end
5
45
  end
@@ -39,7 +39,7 @@ describe Her::Model::Introspection do
39
39
  end
40
40
  end
41
41
 
42
- describe "#nearby_class" do
42
+ describe "#her_nearby_class" do
43
43
  context "for a class inside of a module" do
44
44
  before do
45
45
  spawn_model "Foo::User"
@@ -49,10 +49,10 @@ describe Her::Model::Introspection do
49
49
  end
50
50
 
51
51
  it "returns a sibling class, if found" do
52
- Foo::User.nearby_class("AccessRecord").should == Foo::AccessRecord
53
- AccessRecord.nearby_class("Log").should == Log
54
- Foo::User.nearby_class("Log").should == Log
55
- Foo::User.nearby_class("X").should be_nil
52
+ Foo::User.her_nearby_class("AccessRecord").should == Foo::AccessRecord
53
+ AccessRecord.her_nearby_class("Log").should == Log
54
+ Foo::User.her_nearby_class("Log").should == Log
55
+ Foo::User.her_nearby_class("X").should be_nil
56
56
  end
57
57
  end
58
58
  end
@@ -11,7 +11,8 @@ describe Her::Model::ORM do
11
11
  builder.adapter :test do |stub|
12
12
  stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json] }
13
13
  stub.get("/users") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
14
- stub.get("/admin_users") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
14
+ stub.get("/admin_users") { |env| [200, {}, [{ :admin_id => 1, :name => "Tobias Fünke" }, { :admin_id => 2, :name => "Lindsay Fünke" }].to_json] }
15
+ stub.get("/admin_users/1") { |env| [200, {}, { :admin_id => 1, :name => "Tobias Fünke" }.to_json] }
15
16
  end
16
17
  end
17
18
 
@@ -21,6 +22,7 @@ describe Her::Model::ORM do
21
22
 
22
23
  spawn_model "Foo::AdminUser" do
23
24
  uses_api api
25
+ primary_key :admin_id
24
26
  end
25
27
  end
26
28
 
@@ -28,6 +30,10 @@ describe Her::Model::ORM do
28
30
  @user = Foo::User.find(1)
29
31
  @user.id.should == 1
30
32
  @user.name.should == "Tobias Fünke"
33
+
34
+ @admin = Foo::AdminUser.find(1)
35
+ @admin.id.should == 1
36
+ @admin.name.should == "Tobias Fünke"
31
37
  end
32
38
 
33
39
  it "maps a collection of resources to an array of Ruby objects" do
@@ -49,53 +55,12 @@ describe Her::Model::ORM do
49
55
  @existing_user.new?.should be_false
50
56
  end
51
57
 
52
- it "accepts new resource with strings as hash keys" do
53
- @new_user = Foo::User.new('fullname' => "Tobias Fünke")
54
- @new_user.fullname.should == "Tobias Fünke"
55
- end
56
-
57
- it "handles method missing for getter" do
58
- @new_user = Foo::User.new(:fullname => 'Mayonegg')
59
- lambda { @new_user.unknown_method_for_a_user }.should raise_error(NoMethodError)
60
- expect { @new_user.fullname }.to_not raise_error(NoMethodError)
61
- end
62
-
63
- it "handles method missing for setter" do
64
- @new_user = Foo::User.new
65
- expect { @new_user.fullname = "Tobias Fünke" }.to_not raise_error(NoMethodError)
66
- end
67
-
68
- it "handles method missing for query" do
69
- @new_user = Foo::User.new
70
- expect { @new_user.fullname? }.to_not raise_error(NoMethodError)
71
- end
72
-
73
- it "handles respond_to for getter" do
74
- @new_user = Foo::User.new(:fullname => 'Mayonegg')
75
- @new_user.should_not respond_to(:unknown_method_for_a_user)
76
- @new_user.should respond_to(:fullname)
77
- end
78
-
79
- it "handles respond_to for setter" do
80
- @new_user = Foo::User.new
81
- @new_user.should respond_to(:fullname=)
82
- end
83
-
84
- it "handles respond_to for query" do
85
- @new_user = Foo::User.new
86
- @new_user.should respond_to(:fullname?)
87
- end
58
+ it 'handles new resource with custom primary key' do
59
+ @new_user = Foo::AdminUser.new(:fullname => 'Lindsay Fünke', :id => -1)
60
+ @new_user.should be_new
88
61
 
89
- it "handles has_data? for getter" do
90
- @new_user = Foo::User.new(:fullname => 'Mayonegg')
91
- @new_user.should_not have_data(:unknown_method_for_a_user)
92
- @new_user.should have_data(:fullname)
93
- end
94
-
95
- it "handles get_data for getter" do
96
- @new_user = Foo::User.new(:fullname => 'Mayonegg')
97
- @new_user.get_data(:unknown_method_for_a_user).should be_nil
98
- @new_user.get_data(:fullname).should == 'Mayonegg'
62
+ @existing_user = Foo::AdminUser.find(1)
63
+ @existing_user.should_not be_new
99
64
  end
100
65
  end
101
66
 
@@ -308,26 +273,6 @@ describe Her::Model::ORM do
308
273
  end
309
274
  end
310
275
 
311
- context "assigning new resource data" do
312
- before do
313
- Her::API.setup :url => "https://api.example.com" do |builder|
314
- builder.use Her::Middleware::FirstLevelParseJSON
315
- builder.use Faraday::Request::UrlEncoded
316
- builder.adapter :test do |stub|
317
- stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :active => true }.to_json] }
318
- end
319
- end
320
-
321
- spawn_model "Foo::User"
322
- @user = Foo::User.find(1)
323
- end
324
-
325
- it "handles data update through #assign_attributes" do
326
- @user.assign_attributes :active => true
327
- @user.should be_active
328
- end
329
- end
330
-
331
276
  context "deleting resources" do
332
277
  before do
333
278
  Her::API.setup :url => "https://api.example.com" do |builder|
@@ -436,7 +381,7 @@ describe Her::Model::ORM do
436
381
 
437
382
  it "delegates eql? to ==" do
438
383
  other = Object.new
439
- user.expects(:==).with(other).returns(true)
384
+ user.should_receive(:==).with(other).and_return(true)
440
385
  user.eql?(other).should be_true
441
386
  end
442
387
 
@@ -454,65 +399,6 @@ describe Her::Model::ORM do
454
399
  end
455
400
  end
456
401
 
457
- context "checking dirty attributes" do
458
- before do
459
- Her::API.setup :url => "https://api.example.com" do |builder|
460
- builder.use Her::Middleware::FirstLevelParseJSON
461
- builder.use Faraday::Request::UrlEncoded
462
- builder.adapter :test do |stub|
463
- stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
464
- stub.put("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
465
- stub.post("/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
466
- end
467
- end
468
-
469
- spawn_model "Foo::User" do
470
- attributes :fullname, :email
471
- end
472
- end
473
-
474
- context "for existing resource" do
475
- it "tracks dirty attributes" do
476
- user = Foo::User.find(1)
477
- user.fullname = "Tobias Fünke"
478
- user.fullname_changed?.should be_true
479
- user.email_changed?.should be_false
480
- user.should be_changed
481
- user.save
482
- user.should_not be_changed
483
- end
484
- end
485
-
486
- context "for new resource" do
487
- it "tracks dirty attributes" do
488
- user = Foo::User.new
489
- user.fullname = "Tobias Fünke"
490
- user.fullname_changed?.should be_true
491
- user.should be_changed
492
- user.save
493
- user.should_not be_changed
494
- end
495
- end
496
- end
497
-
498
- context "validating attributes" do
499
- before do
500
- spawn_model "Foo::User" do
501
- attributes :fullname, :email
502
- validates_presence_of :fullname
503
- validates_presence_of :email
504
- end
505
- end
506
-
507
- it "validates attributes when calling #valid?" do
508
- user = Foo::User.new
509
- user.should_not be_valid
510
- user.fullname = "Tobias Fünke"
511
- user.email = "tobias@bluthcompany.com"
512
- user.should be_valid
513
- end
514
- end
515
-
516
402
  context "when include_root_in_json is true" do
517
403
  context "when include_root_in_json is true" do
518
404
  before do
@@ -523,7 +409,7 @@ describe Her::Model::ORM do
523
409
 
524
410
  it "wraps params in the element name" do
525
411
  @new_user = Foo::User.new(:fullname => "Tobias Fünke")
526
- @new_user.to_params.should == { 'user' => { :fullname => "Tobias Fünke" } }
412
+ @new_user.to_params.should == { :user => { :fullname => "Tobias Fünke" } }
527
413
  end
528
414
  end
529
415
 
@@ -137,6 +137,32 @@ describe Her::Model::Paths do
137
137
  end
138
138
  end
139
139
  end
140
+
141
+ context 'custom primary key' do
142
+ before do
143
+ spawn_model 'User' do
144
+ primary_key 'UserId'
145
+ resource_path 'users/:UserId'
146
+ end
147
+
148
+ spawn_model 'Customer' do
149
+ primary_key :customer_id
150
+ resource_path 'customers/:id'
151
+ end
152
+ end
153
+
154
+ describe '#build_request_path' do
155
+ it 'uses the correct primary key attribute' do
156
+ User.build_request_path(:UserId => 'foo').should == 'users/foo'
157
+ User.build_request_path(:id => 'foo').should == 'users'
158
+ end
159
+
160
+ it 'replaces :id with the appropriate primary key' do
161
+ Customer.build_request_path(:customer_id => 'joe').should == 'customers/joe'
162
+ Customer.build_request_path(:id => 'joe').should == 'customers'
163
+ end
164
+ end
165
+ end
140
166
  end
141
167
 
142
168
  context "making subdomain HTTP requests" do
@@ -2,4 +2,21 @@
2
2
  require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
3
 
4
4
  describe "Her::Model and ActiveModel::Validations" do
5
+ context "validating attributes" do
6
+ before do
7
+ spawn_model "Foo::User" do
8
+ attributes :fullname, :email
9
+ validates_presence_of :fullname
10
+ validates_presence_of :email
11
+ end
12
+ end
13
+
14
+ it "validates attributes when calling #valid?" do
15
+ user = Foo::User.new
16
+ user.should_not be_valid
17
+ user.fullname = "Tobias Fünke"
18
+ user.email = "tobias@bluthcompany.com"
19
+ user.should be_valid
20
+ end
21
+ end
5
22
  end