How to Use ActiveResource with Shallow Nested Routes?
Asked Answered
S

4

16

I have a Rails application that has a Company resource with a nested resource Employee. I'm using shallow routing, so to manipulate Employee, my routes are:

GET     /employees/1
PUT     /employees/1
DELETE  /employees/1
POST    /companies/1/employees

How can I create, read, update, and destroy Employees using ActiveResource?

To create employees, I can use:

class Employee < ActiveResource::Base
  self.site = "http://example.com/companies/:company_id"
end

But if I try to do:

e=Employee.find(1, :params => {:company_id => 1})

I get a 404 because the route /companies/:company_id/employees/:id is not defined when shallow routes are used.

To read, edit, and delete employees, I can use:

class Employee < ActiveResource::Base
  self.site = "http://example.com"
end

But then there doesn't seem to be a way to create new Employees, due to the lack of the companies outer route.

One solution would be to define separate CompanyEmployee and Employee classes, but this seems overly complex.

How can I use a single Employee class in ActiveResource to perform all four CRUD operations?

Superstar answered 20/5, 2009 at 18:51 Comment(0)
P
10

There is a protected instance method named collection_path that you could override.

class Employee < ActiveResource::Base
  self.site = "http://example.com"

  def collection_path(options = nil)
    "/companies/#{prefix_options[:company_id]}/#{self.class.collection_name}"
  end
end

You would then be able to this to create employees.

e = Employee.new(:name => "Corey")
e.prefix_options[:company_id] = 1
e.save

It doesn't seem like the prefix_options is documented other than in the clone method so this might change in future releases.

Plated answered 22/5, 2009 at 2:27 Comment(2)
Worked great, except I needed to use "/companies/#{prefix_options[:company_id]}/#{self.class.collection_name},xml" or the POST wasn't interpreted as XML.Superstar
should use self.prefix = "/companies/:company_id/" instead of overriding collection_pathWeinman
A
14

I'm using Rails 3.0.9. You can set the prefix like this:

class Employee < ActiveResource::Base
  self.prefix = "/companies/:company_id/"
end

And then

Employee.find(:all, :params => {:company_id => 99})

or

e = Employee.new(:name => "Corey")
e.prefix_options[:company_id] = 1

It will replace :company_id with the value from prefix_options.

Avron answered 26/7, 2011 at 20:39 Comment(0)
P
10

There is a protected instance method named collection_path that you could override.

class Employee < ActiveResource::Base
  self.site = "http://example.com"

  def collection_path(options = nil)
    "/companies/#{prefix_options[:company_id]}/#{self.class.collection_name}"
  end
end

You would then be able to this to create employees.

e = Employee.new(:name => "Corey")
e.prefix_options[:company_id] = 1
e.save

It doesn't seem like the prefix_options is documented other than in the clone method so this might change in future releases.

Plated answered 22/5, 2009 at 2:27 Comment(2)
Worked great, except I needed to use "/companies/#{prefix_options[:company_id]}/#{self.class.collection_name},xml" or the POST wasn't interpreted as XML.Superstar
should use self.prefix = "/companies/:company_id/" instead of overriding collection_pathWeinman
M
0

See this article: http://blog.flame.org/2009/11/04/activeresource-and-shallow-nested-routes.html

Here, the author proposes to override class method collection_path. This makes sense, since this method is used also by new_element_path and will retrieve the same path in both cases.

Example:

class Employee < ActiveResource::Base
  self.site = "http://example.com"

  def self.collection_path(prefix_options = {},query_options=nil)
    super
    "/companies/#{query_options[:company_id]}/#{collection_name}.#{format.extension}#{query_string(query_options)}"
  end
end

Then you can find employees for a company by doing:

company = Company.find(:first)
Employee.find(:all, :params => {:company_id => company.id })
Mandalay answered 10/8, 2012 at 15:6 Comment(0)
P
0

I found it best to override ActiveResource::Base.element_path with the same functionality as defined in the library, but omitting the use of prefix_options in the returned value. There is no

class Employee < ActiveResource::Base
  self.site = 'http://example.com'
  self.prefix = '/companies/:company_id/'

  # Over-ride method and omit `#{prefix(prefix_options)}` from the returned string
  def self.element_path(id, prefix_options = {}, query_options = nil)
    "/#{collection_name}/#{URI.parser.escape id.to_s}.#{format.extension}"
  end
end

The Employee class will then behave as usual, with no need to assign prefix_options to the instance as suggested in other solutions.

Preprandial answered 24/10, 2018 at 18:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.