Skip to content

Commit d0c1a68

Browse files
committed
Merge pull request #310 from cerebris/has_one_direction
Has one: direction option
2 parents b7700ee + f2265c4 commit d0c1a68

26 files changed

Lines changed: 712 additions & 572 deletions

README.md

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,33 @@ class AuthorResource < JSONAPI::Resource
212212
end
213213
```
214214

215-
#### Associations
215+
#### Relationships
216216

217-
Related resources need to be specified in the resource. These are declared with the `has_one` and the `has_many` methods.
217+
Related resources need to be specified in the resource. These may be declared with the `relationship` or the `has_one`
218+
and the `has_many` methods.
218219

219-
Here's a simple example where a post has a single author and an author can have many posts:
220+
Here's a simple example using the `relationship` method where a post has a single author and an author can have many
221+
posts:
222+
223+
```ruby
224+
class PostResource < JSONAPI::Resource
225+
attribute :title, :body
226+
227+
relationship :author, to: :one
228+
end
229+
```
230+
231+
And the corresponding author:
232+
233+
```ruby
234+
class AuthorResource < JSONAPI::Resource
235+
attribute :name
236+
237+
relationship :posts, to: :many
238+
end
239+
```
240+
241+
And here's the equivalent resources using the `has_one` and `has_many` methods:
220242

221243
```ruby
222244
class PostResource < JSONAPI::Resource
@@ -238,14 +260,17 @@ end
238260

239261
##### Options
240262

241-
The association methods support the following options:
263+
The relationship methods (`relationship`, `has_one`, and `has_many`) support the following options:
242264

243265
* `class_name` - a string specifying the underlying class for the related resource
244266
* `foreign_key` - the method on the resource used to fetch the related resource. Defaults to `<resource_name>_id` for has_one and `<resource_name>_ids` for has_many relationships.
245267
* `acts_as_set` - allows the entire set of related records to be replaced in one operation. Defaults to false if not set.
246-
* `polymorphic` - set to true to identify associations that are polymorphic.
268+
* `polymorphic` - set to true to identify relationships that are polymorphic.
247269
* `relation_name` - the name of the relation to use on the model. A lambda may be provided which allows conditional selection of the relation based on the context.
248270

271+
`to_one` relationships support the additional option:
272+
* `foreign_key_on` - defaults to `:self`. To indicate that the foreign key is on the related resource specify `:related`.
273+
249274
Examples:
250275

251276
```ruby
@@ -288,7 +313,8 @@ class BookResource < JSONAPI::Resource
288313
end
289314
```
290315

291-
The polymorphic association will require the resource and controller to exist, although routing to them will cause an error.
316+
The polymorphic relationship will require the resource and controller to exist, although routing to them will cause an
317+
error.
292318

293319
```ruby
294320
class TaggableResource < JSONAPI::Resource; end
@@ -298,7 +324,8 @@ class TaggablesController < JSONAPI::ResourceController; end
298324
#### Filters
299325

300326
Filters for locating objects of the resource type are specified in the resource definition. Single filters can be
301-
declared using the `filter` method, and multiple filters can be declared with the `filters` method on the resource class.
327+
declared using the `filter` method, and multiple filters can be declared with the `filters` method on the resource
328+
class.
302329

303330
For example:
304331

@@ -311,7 +338,8 @@ class ContactResource < JSONAPI::Resource
311338
end
312339
```
313340

314-
Then a request could pass in a filter for example `http://example.com/contacts?filter[name_last]=Smith` and the system will find all people where the last name exactly matches Smith.
341+
Then a request could pass in a filter for example `http://example.com/contacts?filter[name_last]=Smith` and the system
342+
will find all people where the last name exactly matches Smith.
315343

316344
##### Default Filters
317345

@@ -357,7 +385,7 @@ end
357385
```
358386

359387
When you create a relationship, a method is created to fetch record(s) for that relationship. This method calls
360-
`records_for(association_name)` by default.
388+
`records_for(relationship_name)` by default.
361389

362390
```ruby
363391
class PostResource < JSONAPI::Resource
@@ -375,13 +403,13 @@ end
375403

376404
```
377405

378-
For example, you may want raise an error if the user is not authorized to view the associated records.
406+
For example, you may want raise an error if the user is not authorized to view the related records.
379407

380408
```ruby
381409
class BaseResource < JSONAPI::Resource
382-
def records_for(association_name, options={})
410+
def records_for(relationship_name, options={})
383411
context = options[:context]
384-
records = model.public_send(association_name)
412+
records = model.public_send(relationship_name)
385413

386414
unless context.current_user.can_view?(records)
387415
raise NotAuthorizedError
@@ -564,17 +592,17 @@ Callbacks can also be defined for `JSONAPI::OperationsProcessor` events:
564592
- `:operation`: Any individual operation.
565593
- `:find_operation`: A `find_operation`.
566594
- `:show_operation`: A `show_operation`.
567-
- `:show_association_operation`: A `show_association_operation`.
595+
- `:show_relationship_operation`: A `show_relationship_operation`.
568596
- `:show_related_resource_operation`: A `show_related_resource_operation`.
569597
- `:show_related_resources_operation`: A `show_related_resources_operation`.
570598
- `:create_resource_operation`: A `create_resource_operation`.
571599
- `:remove_resource_operation`: A `remove_resource_operation`.
572600
- `:replace_fields_operation`: A `replace_fields_operation`.
573-
- `:replace_has_one_association_operation`: A `replace_has_one_association_operation`.
574-
- `:create_has_many_association_operation`: A `create_has_many_association_operation`.
575-
- `:replace_has_many_association_operation`: A `replace_has_many_association_operation`.
576-
- `:remove_has_many_association_operation`: A `remove_has_many_association_operation`.
577-
- `:remove_has_one_association_operation`: A `remove_has_one_association_operation`.
601+
- `:replace_has_one_relationship_operation`: A `replace_has_one_relationship_operation`.
602+
- `:create_has_many_relationship_operation`: A `create_has_many_relationship_operation`.
603+
- `:replace_has_many_relationship_operation`: A `replace_has_many_relationship_operation`.
604+
- `:remove_has_many_relationship_operation`: A `remove_has_many_relationship_operation`.
605+
- `:remove_has_one_relationship_operation`: A `remove_has_one_relationship_operation`.
578606

579607
The operation callbacks have access to two meta data hashes, `@operations_meta` and `@operation_meta`, two links hashes,
580608
`@operations_links` and `@operation_links`, the full list of `@operations`, each individual `@operation` and the
@@ -875,7 +903,7 @@ An array of resources. Nested resources can be specified with dot notation.
875903
A hash of resource types and arrays of fields for each resource type.
876904

877905
*Purpose*: determines which fields are serialized for a resource type. This encompasses both attributes and
878-
association ids in the links section for a resource. Fields are global for a resource type.
906+
relationship ids in the links section for a resource. Fields are global for a resource type.
879907

880908
*Example*: ```fields: { people: [:email, :comments], posts: [:title, :author], comments: [:body, :post]}```
881909

@@ -904,7 +932,7 @@ JR has a couple of helper methods available to assist you with setting up routes
904932
##### `jsonapi_resources`
905933

906934
Like `resources` in `ActionDispatch`, `jsonapi_resources` provides resourceful routes mapping between HTTP verbs and URLs
907-
and controller actions. This will also setup mappings for relationship URLs for a resource's associations. For example:
935+
and controller actions. This will also setup mappings for relationship URLs for a resource's relationships. For example:
908936

909937
```ruby
910938
Rails.application.routes.draw do
@@ -917,20 +945,20 @@ gives the following routes
917945

918946
```
919947
Prefix Verb URI Pattern Controller#Action
920-
contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#show_association {:association=>"phone_numbers"}
921-
POST /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#create_association {:association=>"phone_numbers"}
922-
DELETE /contacts/:contact_id/relationships/phone-numbers/:keys(.:format) contacts#destroy_association {:association=>"phone_numbers"}
923-
contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:association=>"phone_numbers", :source=>"contacts"}
948+
contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#show_relationship {:relationship=>"phone_numbers"}
949+
POST /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#create_relationship {:relationship=>"phone_numbers"}
950+
DELETE /contacts/:contact_id/relationships/phone-numbers/:keys(.:format) contacts#destroy_relationship {:relationship=>"phone_numbers"}
951+
contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:relationship=>"phone_numbers", :source=>"contacts"}
924952
contacts GET /contacts(.:format) contacts#index
925953
POST /contacts(.:format) contacts#create
926954
contact GET /contacts/:id(.:format) contacts#show
927955
PATCH /contacts/:id(.:format) contacts#update
928956
PUT /contacts/:id(.:format) contacts#update
929957
DELETE /contacts/:id(.:format) contacts#destroy
930-
phone_number_relationships_contact GET /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#show_association {:association=>"contact"}
931-
PUT|PATCH /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#update_association {:association=>"contact"}
932-
DELETE /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#destroy_association {:association=>"contact"}
933-
phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:association=>"contact", :source=>"phone_numbers"}
958+
phone_number_relationships_contact GET /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#show_relationship {:relationship=>"contact"}
959+
PUT|PATCH /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#update_relationship {:relationship=>"contact"}
960+
DELETE /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#destroy_relationship {:relationship=>"contact"}
961+
phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:relationship=>"contact", :source=>"phone_numbers"}
934962
phone_numbers GET /phone-numbers(.:format) phone_numbers#index
935963
POST /phone-numbers(.:format) phone_numbers#create
936964
phone_number GET /phone-numbers/:id(.:format) phone_numbers#show
@@ -995,9 +1023,9 @@ end
9951023
Gives the following routes:
9961024

9971025
```
998-
contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#show_association {:association=>"phone_numbers"}
999-
POST /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#create_association {:association=>"phone_numbers"}
1000-
DELETE /contacts/:contact_id/relationships/phone-numbers/:keys(.:format) contacts#destroy_association {:association=>"phone_numbers"}
1026+
contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#show_relationship {:relationship=>"phone_numbers"}
1027+
POST /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#create_relationship {:relationship=>"phone_numbers"}
1028+
DELETE /contacts/:contact_id/relationships/phone-numbers/:keys(.:format) contacts#destroy_relationship {:relationship=>"phone_numbers"}
10011029
contacts GET /contacts(.:format) contacts#index
10021030
POST /contacts(.:format) contacts#create
10031031
contact GET /contacts/:id(.:format) contacts#show
@@ -1007,7 +1035,7 @@ contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/p
10071035
10081036
```
10091037

1010-
The new routes allow you to show, create and destroy the associations between resources.
1038+
The new routes allow you to show, create and destroy the relationships between resources.
10111039

10121040
###### `jsonapi_related_resources`
10131041

@@ -1026,7 +1054,7 @@ gives the following routes:
10261054

10271055
```
10281056
Prefix Verb URI Pattern Controller#Action
1029-
contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:association=>"phone_numbers", :source=>"contacts"}
1057+
contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:relationship=>"phone_numbers", :source=>"contacts"}
10301058
contacts GET /contacts(.:format) contacts#index
10311059
POST /contacts(.:format) contacts#create
10321060
contact GET /contacts/:id(.:format) contacts#show
@@ -1054,7 +1082,7 @@ gives the following routes:
10541082

10551083
```
10561084
Prefix Verb URI Pattern Controller#Action
1057-
phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:association=>"contact", :source=>"phone_numbers"}
1085+
phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:relationship=>"contact", :source=>"phone_numbers"}
10581086
phone_numbers GET /phone-numbers(.:format) phone_numbers#index
10591087
POST /phone-numbers(.:format) phone_numbers#create
10601088
phone_number GET /phone-numbers/:id(.:format) phone_numbers#show

lib/jsonapi-resources.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
require 'jsonapi/request'
1616
require 'jsonapi/operations_processor'
1717
require 'jsonapi/active_record_operations_processor'
18-
require 'jsonapi/association'
18+
require 'jsonapi/relationship'
1919
require 'jsonapi/include_directives'
2020
require 'jsonapi/operation_result'
2121
require 'jsonapi/operation_results'

lib/jsonapi/acts_as_resource_controller.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module ActsAsResourceController
55
extend ActiveSupport::Concern
66

77
included do
8-
before_filter :ensure_correct_media_type, only: [:create, :update, :create_association, :update_association]
8+
before_filter :ensure_correct_media_type, only: [:create, :update, :create_relationship, :update_relationship]
99
append_before_filter :setup_request
1010
after_filter :setup_response
1111
end
@@ -18,19 +18,19 @@ def show
1818
process_request_operations
1919
end
2020

21-
def show_association
21+
def show_relationship
2222
process_request_operations
2323
end
2424

2525
def create
2626
process_request_operations
2727
end
2828

29-
def create_association
29+
def create_relationship
3030
process_request_operations
3131
end
3232

33-
def update_association
33+
def update_relationship
3434
process_request_operations
3535
end
3636

@@ -42,7 +42,7 @@ def destroy
4242
process_request_operations
4343
end
4444

45-
def destroy_association
45+
def destroy_relationship
4646
process_request_operations
4747
end
4848

lib/jsonapi/exceptions.rb

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ def errors
7373
end
7474
end
7575

76-
class HasManySetReplacementForbidden < Error
76+
class ToManySetReplacementForbidden < Error
7777
def errors
7878
[JSONAPI::Error.new(code: JSONAPI::FORBIDDEN,
7979
status: :forbidden,
8080
title: 'Complete replacement forbidden',
81-
detail: 'Complete replacement forbidden for this association')]
81+
detail: 'Complete replacement forbidden for this relationship')]
8282
end
8383
end
8484

@@ -188,17 +188,17 @@ def errors
188188
end
189189

190190
class InvalidInclude < Error
191-
attr_accessor :association, :resource
192-
def initialize(resource, association)
191+
attr_accessor :relationship, :resource
192+
def initialize(resource, relationship)
193193
@resource = resource
194-
@association = association
194+
@relationship = relationship
195195
end
196196

197197
def errors
198198
[JSONAPI::Error.new(code: JSONAPI::INVALID_INCLUDE,
199199
status: :bad_request,
200200
title: 'Invalid field',
201-
detail: "#{association} is not a valid association of #{resource}")]
201+
detail: "#{relationship} is not a valid relationship of #{resource}")]
202202
end
203203
end
204204

@@ -294,11 +294,11 @@ def errors
294294
end
295295

296296
class ValidationErrors < Error
297-
attr_reader :error_messages, :resource_associations
297+
attr_reader :error_messages, :resource_relationships
298298

299299
def initialize(resource)
300300
@error_messages = resource.model.errors.messages
301-
@resource_associations = resource.class._associations.keys
301+
@resource_relationships = resource.class._relationships.keys
302302
@key_formatter = JSONAPI.configuration.key_formatter
303303
end
304304

@@ -322,11 +322,11 @@ def json_api_error(attr_key, message)
322322
source: { pointer: pointer(attr_key) })
323323
end
324324

325-
def pointer(attr_or_association_name)
326-
if resource_associations.include?(attr_or_association_name)
327-
"/data/relationships/#{attr_or_association_name}"
325+
def pointer(attr_or_relationship_name)
326+
if resource_relationships.include?(attr_or_relationship_name)
327+
"/data/relationships/#{attr_or_relationship_name}"
328328
else
329-
"/data/attributes/#{attr_or_association_name}"
329+
"/data/attributes/#{attr_or_relationship_name}"
330330
end
331331
end
332332
end

lib/jsonapi/link_builder.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ def query_link(query_params)
3131
"#{ primary_resources_url }?#{ query_params.to_query }"
3232
end
3333

34-
def relationships_related_link(source, association)
35-
"#{ self_link(source) }/#{ route_for_association(association) }"
34+
def relationships_related_link(source, relationship)
35+
"#{ self_link(source) }/#{ route_for_relationship(relationship) }"
3636
end
3737

38-
def relationships_self_link(source, association)
39-
"#{ self_link(source) }/relationships/#{ route_for_association(association) }"
38+
def relationships_self_link(source, relationship)
39+
"#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
4040
end
4141

4242
def self_link(source)
@@ -134,8 +134,8 @@ def regular_resource_url(source)
134134
"#{ base_url }#{ regular_resource_path(source) }"
135135
end
136136

137-
def route_for_association(association)
138-
format_route(association.name)
137+
def route_for_relationship(relationship)
138+
format_route(relationship.name)
139139
end
140140
end
141141
end

0 commit comments

Comments
 (0)