benjaminkrause-restful 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGES.markdown +8 -0
  2. data/LICENSE.markdown +22 -0
  3. data/README.markdown +123 -0
  4. data/Rakefile +22 -0
  5. data/TODO.markdown +9 -0
  6. data/init.rb +1 -0
  7. data/lib/restful.rb +65 -0
  8. data/lib/restful/apimodel/attribute.rb +17 -0
  9. data/lib/restful/apimodel/collection.rb +22 -0
  10. data/lib/restful/apimodel/link.rb +21 -0
  11. data/lib/restful/apimodel/map.rb +41 -0
  12. data/lib/restful/apimodel/resource.rb +23 -0
  13. data/lib/restful/converters/active_record.rb +131 -0
  14. data/lib/restful/rails.rb +22 -0
  15. data/lib/restful/rails/action_controller.rb +14 -0
  16. data/lib/restful/rails/active_record/configuration.rb +167 -0
  17. data/lib/restful/rails/active_record/metadata_tools.rb +106 -0
  18. data/lib/restful/serializers/atom_like_serializer.rb +51 -0
  19. data/lib/restful/serializers/base.rb +57 -0
  20. data/lib/restful/serializers/hash_serializer.rb +59 -0
  21. data/lib/restful/serializers/json_serializer.rb +20 -0
  22. data/lib/restful/serializers/params_serializer.rb +46 -0
  23. data/lib/restful/serializers/xml_serializer.rb +161 -0
  24. data/rails/init.rb +1 -0
  25. data/restful.gemspec +17 -0
  26. data/test/converters/active_record_converter_test.rb +122 -0
  27. data/test/converters/basic_types_converter_test.rb +48 -0
  28. data/test/fixtures/models/paginated_collection.rb +4 -0
  29. data/test/fixtures/models/person.rb +29 -0
  30. data/test/fixtures/models/pet.rb +5 -0
  31. data/test/fixtures/models/wallet.rb +5 -0
  32. data/test/fixtures/people.json.yaml +94 -0
  33. data/test/fixtures/people.xml.yaml +123 -0
  34. data/test/fixtures/pets.json.yaml +20 -0
  35. data/test/fixtures/pets.xml.yaml +31 -0
  36. data/test/rails/active_record_metadata_test.rb +23 -0
  37. data/test/rails/configuration_test.rb +40 -0
  38. data/test/rails/restful_publish_test.rb +52 -0
  39. data/test/serializers/atom_serializer_test.rb +33 -0
  40. data/test/serializers/json_serializer_test.rb +82 -0
  41. data/test/serializers/params_serializer_test.rb +76 -0
  42. data/test/serializers/xml_serializer_test.rb +51 -0
  43. data/test/test_helper.rb +147 -0
  44. metadata +98 -0
@@ -0,0 +1,5 @@
1
+ class Pet < ActiveRecord::Base
2
+ belongs_to :owner, :class_name => "Person", :foreign_key => "person_id"
3
+
4
+ apiable
5
+ end
@@ -0,0 +1,5 @@
1
+ class Wallet < ActiveRecord::Base
2
+ belongs_to :person
3
+
4
+ apiable
5
+ end
@@ -0,0 +1,94 @@
1
+ bloggs:
2
+ |
3
+ {
4
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
5
+ "wallet": {
6
+ "restful_url": "http://example.com:3000/wallets/<%= @person.wallet.to_param %>",
7
+ "contents": "an old photo, 5 euros in coins"
8
+ },
9
+ "current_location": "Under a tree",
10
+ "name": "Joe Bloggs",
11
+ "pets": [ {
12
+ "restful_url": "http://example.com:3000/pets/<%= @person.pets.first.to_param %>",
13
+ "name": "mietze"
14
+ }],
15
+ "created_at": "<%= @person.created_at.xmlschema %>"
16
+ }
17
+
18
+ bloggs_with_oldest_pet:
19
+ |
20
+ {
21
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
22
+ "oldest_pet": {
23
+ "restful_url": "http://example.com:3000/pets/<%= @person.pets.first.to_param %>",
24
+ "name": "mietze"
25
+ }
26
+ }
27
+
28
+ bloggs_with_birthday:
29
+ |
30
+ {
31
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
32
+ "birthday": "<%= @person.birthday.to_s(:db) %>"
33
+ }
34
+
35
+ bloggs_with_pets_ages_hash:
36
+ |
37
+ {
38
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
39
+ "pets_ages_hash": {
40
+ "mietze": 200,
41
+ "motze": 100
42
+ }
43
+ }
44
+ bloggs_with_has_pets:
45
+ |
46
+ {
47
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
48
+ "has_pets": true
49
+ }
50
+ bloggs_with_hasno_pets:
51
+ |
52
+ {
53
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
54
+ "has_pets": false
55
+ }
56
+
57
+
58
+ bloggs_da_pet_hater:
59
+ |
60
+ {
61
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
62
+ "wallet": {
63
+ "restful_url": "http://example.com:3000/wallets/<%= @person.wallet.to_param %>",
64
+ "contents": "an old photo, 5 euros in coins"
65
+ },
66
+ "current_location": "Under a tree",
67
+ "name": "Joe Bloggs",
68
+ "pets": [],
69
+ "created_at": "<%= @person.created_at.xmlschema %>"
70
+ }
71
+
72
+ hash_with_person:
73
+ |
74
+ {
75
+ "total_hits": 1,
76
+ "a_person": {
77
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
78
+ "name": "Joe Bloggs"
79
+ }
80
+ }
81
+
82
+ hash_with_people:
83
+ |
84
+ {
85
+ "total_hits": 2,
86
+ "people": [{
87
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
88
+ "name": "Joe Bloggs"
89
+ },
90
+ {
91
+ "restful_url": "http://example.com:3000/people/<%= @person.to_param %>",
92
+ "name": "Joe Bloggs"
93
+ }]
94
+ }
@@ -0,0 +1,123 @@
1
+ joe_bloggs:
2
+ |
3
+ <?xml version="1.0" encoding="UTF-8"?>
4
+ <person>
5
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
6
+ <name>Joe Bloggs</name>
7
+ <wallet-restful-url type="link">http://example.com:3000/wallets/<%= @wallet.id %></wallet-restful-url>
8
+ </person>
9
+
10
+ no_wallet:
11
+ |
12
+ <?xml version="1.0" encoding="UTF-8"?>
13
+ <person>
14
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
15
+ <location-sentence><%= @person.location_sentence %></location-sentence>
16
+ </person>
17
+
18
+ with_oldest_pet:
19
+ |
20
+ <?xml version="1.0" encoding="UTF-8"?>
21
+ <person>
22
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
23
+ <oldest-pet>
24
+ <restful-url type="link">http://example.com:3000/pets/<%= @person.oldest_pet.id %></restful-url>
25
+ <name><%= @person.oldest_pet.name %></name>
26
+ <person-restful-url type="link">http://example.com:3000/people/<%= @person.id %></person-restful-url>
27
+ </oldest-pet>
28
+ </person>
29
+
30
+ with_oldest_pet_species:
31
+ |
32
+ <?xml version="1.0" encoding="UTF-8"?>
33
+ <person>
34
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
35
+ <oldest-pet>
36
+ <restful-url type="link">http://example.com:3000/pets/<%= @person.oldest_pet.id %></restful-url>
37
+ <species type="integer"><%= @person.oldest_pet.species %></species>
38
+ </oldest-pet>
39
+ </person>
40
+
41
+ joe_with_birthday:
42
+ |
43
+ <?xml version="1.0" encoding="UTF-8"?>
44
+ <person>
45
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
46
+ <birthday type="date"><%= @person.birthday.to_s(:db) %></birthday>
47
+ </person>
48
+
49
+ joe_with_zwiebelleder:
50
+ |
51
+ <?xml version="1.0" encoding="UTF-8"?>
52
+ <person>
53
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
54
+ <wallet nil="true"></wallet>
55
+ </person>
56
+
57
+ atom_person:
58
+ |
59
+ <?xml version="1.0" encoding="UTF-8"?>
60
+ <person xml:base="http://example.com:3000">
61
+ <created-at><%= @person.created_at.xmlschema %></created-at>
62
+ <link rel="self" href="/people/<%= @person.id %>"/>
63
+ <name>Joe Bloggs</name>
64
+ <current-location>Under a tree</current-location>
65
+ <pets>
66
+ <pet>
67
+ <link rel="self" href="/pets/<%= @pet.id %>"/>
68
+ <name>mietze</name>
69
+ </pet>
70
+ </pets>
71
+ <wallet>
72
+ <link rel="self" href="/wallets/<%= @wallet.id %>"/>
73
+ <contents>an old photo, 5 euros in coins</contents>
74
+ </wallet>
75
+ </person>
76
+
77
+ verbose_with_pets:
78
+ |
79
+ <?xml version="1.0" encoding="UTF-8"?>
80
+ <person>
81
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
82
+ <name>Joe Bloggs</name>
83
+ <created-at type="datetime"><%= @person.created_at.xmlschema %></created-at>
84
+ <current-location>Under a tree</current-location>
85
+ <pets type="array">
86
+ <pet>
87
+ <restful-url type="link">http://example.com:3000/pets/<%= @pet.id %></restful-url>
88
+ <name>mietze</name>
89
+ </pet>
90
+ </pets>
91
+ <wallet-restful-url type="link" nil="true"></wallet-restful-url>
92
+ </person>
93
+
94
+ with_pets_and_expanded_wallet:
95
+ |
96
+ <?xml version="1.0" encoding="UTF-8"?>
97
+ <person>
98
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
99
+ <name>Joe Bloggs</name>
100
+ <created-at type="datetime"><%= @person.created_at.xmlschema %></created-at>
101
+ <current-location>Under a tree</current-location>
102
+ <pets type="array">
103
+ <pet>
104
+ <restful-url type="link">http://example.com:3000/pets/<%= @pet.id %></restful-url>
105
+ <name>mietze</name>
106
+ </pet>
107
+ </pets>
108
+ <wallet>
109
+ <restful-url type="link">http://example.com:3000/wallets/<%= @wallet.id %></restful-url>
110
+ <contents>an old photo, 5 euros in coins</contents>
111
+ </wallet>
112
+ </person>
113
+
114
+ hashy_person:
115
+ |
116
+ <?xml version="1.0" encoding="UTF-8"?>
117
+ <person>
118
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
119
+ <pets-ages-hash>
120
+ <mietze>200</mietze>
121
+ <motze>100</motze>
122
+ </pets-ages-hash>
123
+ </person>
@@ -0,0 +1,20 @@
1
+ pets_array:
2
+ |
3
+ [
4
+ {
5
+ "restful_url": "http://example.com:3000/pets/<%= @person.pets.first.to_param %>",
6
+ "name": "mietze"
7
+ }
8
+ ]
9
+
10
+ pets_array_with_total_entries:
11
+ |
12
+ {
13
+ "total_entries": 1001,
14
+ "pets": [
15
+ {
16
+ "restful_url": "http://example.com:3000/pets/<%= @person.pets.first.to_param %>",
17
+ "name": "mietze"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,31 @@
1
+ nameless_pet:
2
+ |
3
+ <?xml version="1.0" encoding="UTF-8"?>
4
+ <pet>
5
+ <restful-url type="link">http://example.com:3000/pets/<%= @pet.id %></restful-url>
6
+ <owner>
7
+ <restful-url type="link">http://example.com:3000/people/<%= @person.id %></restful-url>
8
+ <name>Joe Bloggs</name>
9
+ <current-location>Under a tree</current-location>
10
+ <wallet-restful-url type="link">http://example.com:3000/wallets/<%= @wallet.id %></wallet-restful-url>
11
+ </owner>
12
+ </pet>
13
+
14
+ gracie:
15
+ |
16
+ <?xml version="1.0" encoding="UTF-8"?>
17
+ <pet>
18
+ <person-restful-url type="link">http://example.com:3000/people/<%= @person.id %></person-restful-url>
19
+ <species>123</species>
20
+ <name>Gracie</name>
21
+ </pet>
22
+
23
+ pets_array:
24
+ |
25
+ <?xml version="1.0" encoding="UTF-8"?>
26
+ <pets>
27
+ <pet>
28
+ <restful-url type="link">http://example.com:3000/pets/<%= @pet.id %></restful-url>
29
+ <name>mietze</name>
30
+ </pet>
31
+ </pets>
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ context "active record metadata" do
4
+ setup do
5
+ Person.restful_publish(:name, :pets => [:name, :species])
6
+ Pet.restful_publish(:person_id, :name) # person_id gets converted to a link automagically.
7
+
8
+ @person = Person.create(:name => "Jimmy Jones", :current_location => "Under a tree")
9
+ @pet = @person.pets.create(:name => "Mietze", :species => "cat")
10
+ end
11
+
12
+ teardown do
13
+ reset_config
14
+ end
15
+
16
+ specify "should be able to convert a collection to an array of resources" do
17
+ resources = Restful::Rails.tools.convert_collection_to_resources(@person, :pets, Restful.cfg)
18
+ pet = resources.first
19
+
20
+ resources.size.should.equal 1
21
+ pet.url.should.equal @pet.to_restful.url
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ context "Configuration" do
4
+ specify "should have an empty whitelist if only restful options are passed in" do
5
+ config = Restful.cfg
6
+ config.restful_options[:expansion] = :expanded
7
+ config.whitelisted.should.empty
8
+ end
9
+
10
+ specify "should return fields without restful_options via whitelisted" do
11
+ config = Restful.cfg([:one, :two])
12
+ config.restful_options[:expansion] = :expanded
13
+ config.whitelisted.should.not.include :restful_options
14
+ end
15
+
16
+ specify "should have 0 whitelisted fields if none were specified" do
17
+ end
18
+
19
+ specify "should know if it has restful_options" do
20
+ config = Restful.cfg([:one, :two])
21
+ config.restful_options[:expansion] = :expanded
22
+ config.restful_options.should.not.empty
23
+ end
24
+
25
+ specify "should be able to handle nested whitelist attributes" do
26
+ config = Restful.cfg(:one, :two => [:a, :b])
27
+ config.nested(:two).whitelisted.should.== [:a,:b]
28
+ end
29
+
30
+ specify "should know which attributes are published" do
31
+ config = Restful.cfg(:one, :two => [:a, :b])
32
+ config.published?(:two).should.== true
33
+ config.published?(:one).should.== true
34
+ end
35
+
36
+ specify "should know if it is nested" do
37
+ config = Restful.cfg(:one, :restful_options => {:nested => true})
38
+ config.nested?.should.== true
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ context "restful publish" do
4
+ teardown do
5
+ reset_config
6
+ end
7
+
8
+ specify "should result in a method .published?(:attr_key) return true for published attributes" do
9
+ Pet.restful_publish(:person_id, :name) # person_id gets converted to a link automagically.
10
+
11
+ Pet.restful_config.published?(:name).should.equal true
12
+ Pet.restful_config.published?(:pets).should.equal false
13
+ Pet.restful_config.published?(:species).should.equal false
14
+ end
15
+
16
+ specify "should have restful_options as an empty hash after calling restful_publish" do
17
+ Person.restful_publish(:name, :pets => [:name, :species])
18
+ Person.restful_config.restful_options.should.==({})
19
+ end
20
+
21
+ specify "should include attributes when publishe parameter is passed to to_restful" do
22
+ Person.restful_publish(:name)
23
+ Pet.restful_publish(:name)
24
+
25
+ @person = Person.create
26
+ @pet = @person.pets.create(:name => "Mietze")
27
+
28
+ @pet.to_restful(:include => :owner).values.map(&:name).should.include :owner
29
+ Pet.restful_config.whitelisted.should.not.include? :owner
30
+ end
31
+ end
32
+
33
+ context "api publishing with nesting" do
34
+ teardown do
35
+ reset_config
36
+ end
37
+
38
+ specify "should result in a method .published?(:attr_key) return true for nested attributes" do
39
+ Person.restful_publish(:name, :pets => [:name, :species])
40
+ Person.restful_config.published?(:pets).should.equal true
41
+ end
42
+
43
+ specify "should be invoke to_restful on the nested model with the specified nested attributes" do
44
+ Person.restful_publish(:name, :pets => [:name, :species])
45
+ @person = Person.create(:name => "Joe Bloggs", :current_location => "Under a tree")
46
+ @pet = @person.pets.create(:name => "Mietze", :species => "cat")
47
+
48
+ Pet.any_instance.expects(:to_restful).with { |arg| arg.whitelisted == [:name, :species] }
49
+ @person.to_restful
50
+ end
51
+ end
52
+
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ context "params serializer" do
4
+
5
+ setup do
6
+ Person.restful_publish(:name, :current_location, :pets, :wallet, :created_at)
7
+ Pet.restful_publish(:name)
8
+ Wallet.restful_publish(:contents)
9
+
10
+ @person = Person.create(:name => "Joe Bloggs", :current_location => "Under a tree")
11
+ @pet = @person.pets.create(:species => "cat", :age => 200, :name => "mietze")
12
+ @wallet = @person.wallet = Wallet.new(:contents => "an old photo, 5 euros in coins")
13
+ @person.save
14
+ end
15
+
16
+ teardown do
17
+ reset_config
18
+ end
19
+
20
+ specify "serialize to xml, atom style" do
21
+ xml_should_eql_fixture(@person.to_restful_atom_like, "people", :atom_person)
22
+ end
23
+
24
+ specify "deserialize from atom style xml" do
25
+ restful = @pet.to_restful
26
+ expected = restful.serialize(:atom_like)
27
+ serializer = Restful::Serializers::AtomLikeSerializer.new
28
+ resource = serializer.deserialize(expected)
29
+ actual = serializer.serialize(resource)
30
+
31
+ xml_should_eql(expected, actual)
32
+ end
33
+ end
@@ -0,0 +1,82 @@
1
+ require File.dirname(__FILE__) + '/../test_helper.rb'
2
+
3
+ context "json serializer" do
4
+
5
+ setup do
6
+ Person.restful_publish(:name, :current_location, :pets, :wallet, :created_at)
7
+ Pet.restful_publish(:name)
8
+ Wallet.restful_publish(:contents)
9
+
10
+ @person = Person.create(:name => "Joe Bloggs", :current_location => "Under a tree", :birthday => "2009-09-19")
11
+ @pet = @person.pets.create(:species => "cat", :age => 200, :name => "mietze")
12
+ @wallet = @person.wallet = Wallet.new(:contents => "an old photo, 5 euros in coins")
13
+ @person.save
14
+ end
15
+
16
+ teardown { reset_config }
17
+
18
+ specify "serialize to json" do
19
+ json_should_eql_fixture(@person.to_restful_json, "people", :bloggs)
20
+ end
21
+
22
+ specify "should be able to serialize objects with empty collections" do
23
+ @person.pets = []
24
+
25
+ assert_nothing_raised do
26
+ json_should_eql_fixture(@person.to_restful_json, "people", :bloggs_da_pet_hater)
27
+ end
28
+ end
29
+
30
+ specify "should serialize date type correctly" do
31
+ json_should_eql_fixture(@person.to_restful_json(:birthday), "people", :bloggs_with_birthday)
32
+ end
33
+
34
+ specify "should not bug out on nil values in date fields" do
35
+ person = Person.create :created_at => nil, :birthday => nil, :last_login => nil
36
+ person.created_at = nil
37
+ expected = "{\"birthday\":null,\"restful_url\":\"http://example.com:3000/people/#{person.to_param}\",\"last_login\":null,\"created_at\":null}"
38
+ assert_nothing_raised do
39
+ actual = person.to_restful_json([:created_at, :birthday, :last_login])
40
+ json_should_eql(actual, expected)
41
+ end
42
+ end
43
+
44
+ specify "should serialize hashes correctly" do
45
+ @person.pets.create(:species => "cat", :age => 100, :name => "motze")
46
+ json_should_eql_fixture(@person.to_restful_json(:pets_ages_hash), "people", :bloggs_with_pets_ages_hash)
47
+ end
48
+
49
+ specify "should render boolean values correctly" do
50
+ json_should_eql_fixture(@person.to_restful_json(:has_pets), "people", :bloggs_with_has_pets)
51
+ @person.pets = []
52
+ @person.save!
53
+ json_should_eql_fixture(@person.to_restful_json(:has_pets), "people", :bloggs_with_hasno_pets)
54
+ end
55
+
56
+ specify "should not ever use dashes as hash keys but underscores" do
57
+ assert_nothing_raised do
58
+ json_should_eql_fixture(@person.to_restful_json(:oldest_pet), "people", :bloggs_with_oldest_pet)
59
+ end
60
+ end
61
+
62
+ specify "should serialize collections correctly" do
63
+ json_should_eql_fixture(@person.pets.to_restful_json, "pets", :pets_array)
64
+ end
65
+
66
+ specify "should be able to serialize collections with total entries info" do
67
+ pets = PaginatedCollection.new(@person.pets)
68
+ pets.total_entries = 1001
69
+
70
+ json_should_eql_fixture(pets.to_restful_json, "pets", :pets_array_with_total_entries)
71
+ end
72
+
73
+ specify "should be able to serialize a map" do
74
+ Person.restful_publish(:name)
75
+ json_should_eql_fixture({ "total_hits" => 1, "a_person" => @person }.to_restful_json, "people", :hash_with_person)
76
+ end
77
+
78
+ specify "should be able to serialize a map with arrays as values" do
79
+ Person.restful_publish(:name)
80
+ json_should_eql_fixture({ "total_hits" => 2, "people" => [ @person, @person ] }.to_restful_json, "people", :hash_with_people)
81
+ end
82
+ end