REST nested resources

Hi

I'm porting an existing project to fit the REST philosofie. I have 3
resources that are nested: users, companies and roles. A user has and
belongs to multiple companies, and depening on the company, a role will
be assigned.

So I have an association table: users_companies with the following
fields: user_id, company_id, role_id. But how do I assign a user to a
company with a role the REST way?

Normally you would create a new controller, but I would like to do the
assignments in the user form.

Anyone that has some experience on this?

Thank you in advance.

Michael Rigart wrote:

Hi

I'm porting an existing project to fit the REST philosofie. I have 3
resources that are nested: users, companies and roles. A user has and
belongs to multiple companies, and depening on the company, a role will
be assigned.

So I have an association table: users_companies with the following
fields: user_id, company_id, role_id. But how do I assign a user to a
company with a role the REST way?

Normally you would create a new controller, but I would like to do the
assignments in the user form.

Anyone that has some experience on this?

Thank you in advance.

Sorry for adding this, but the association should also made possible on
the company form.

I thought I had an answer until I saw this addendum :slight_smile:

Can you explain a little more what the company form is trying to do?

Jeff

Jeff Cohen wrote:

Michael Rigart wrote:

Sorry for adding this, but the association should also made possible on
the company form.

I thought I had an answer until I saw this addendum :slight_smile:

Can you explain a little more what the company form is trying to do?

Jeff

Sorry Jeff if I wasn't clear enough.
The company and user form are basically used to create and / or edit a
company or user.

Under the user details, there will be another "section" where the
administrator can assign the companies to the user he is creating
/editing with a sertain role.

On the other hand, when he is creating a new company, under the company
details, there will be another "section" where the admin can assign
users to the company he is creating / editing with a sertain role.

So its a litle complex and I can't see how to do this as restful as
possible.

Thank you for your intereset.

Anyone who has an idea this one?

It sounds like you need to update multiple kinds of resources from one
form, which unfortunately Rails doesn't handle very well yet (there
are some proposed changes in edge that might help this, though).

I'd recommend posting to the controller that seems to represent the
"main" resource (the user or company, as needed). I'm imagining
you've got some checkboxes for users (or companies) that will come
along for the ride.

That being the case, I would suggest that your create action (say, for
users) pull out the company list from the params hash first:

# remove company IDs from the params hash
# and keep the array of company IDs for later use
# You might not need this step depending on
# how your checkbox names are setup
company_ids = params[:user].delete :company

Now the params hash has just the user info remaining in it:

user = User.build(params[:user])

Now you can associate the user to the companies represented by the ids
in one fell swoop:

user.company_ids = company_ids

Then save everything:

user.save # should save the relationships as well, I believe

Although it might seem "non-restful" to be assigning the company
relationships from within the create action, I can't think of a better
way to do it, and to me it actually seems reasonable. If the UI is
specifying that creating a user includes assigning some associations,
then I think it's legit to do this all within the create action.

What do you think?

Jeff

Hi Jeff

sorry for the late reply, but I have come to some solution that fits my
needs. But their are still some minor code details that need some
upgrading becouse they can lead to errors due to possible wrong user
input.

As you said, I send the data to the controller that represents the main
resource (in the example I'm giving now that will be the company
resource).

One thing that you need to know is that the related users table is build
through Javascript. This is becouse you can add/delete more/less users
dynamically.
For that, I just create a simple table, and add the rules dynamically in
the body. So on the end of the page, I do the following:

<% @company.company_roles.each do |role| %>
  Company.addAssociatedUser(<%= role.to_json(:only => [ :id, :user_id,
:role_id ]) %>);
<% end %>

The addAssociateUser mehthod adds the associations dynamicaly. On my
form, I can add new rules by calling the method again, but without
parameters.

So far, so good, that works. Now I submit my form, and all the data get
send to the create/update action. To make it simple, I will show the
create action:

  def create
    @company = Company.new(params[:company])

    if @company.save && save_user_role_associations
      flash[:message] = l("company_create_response")
      redirect_to(companies_url())
    else
      render :action => "new"
    end
  end

So, I create my company object and save it. But, it also calls for a
private action "save_user_role_associations". This action looks like
this:

  def save_user_role_associations
    saved_ids = []

    params[:company_user_association].each do |key, item_vals|
      if item_vals[:id].to_i > 0
        association = CompanyRole.find(item_vals[:id])
        association.update_attributes(item_vals)
      else
        association = CompanyRole.new(:company_id => @company.id,
:role_id => item_vals[:role_id], :user_id => item_vals[:user_id])
        association.save
      end

      item_vals.delete :id
      saved_ids << association.id
    end

    CompanyRole.find(:all, :conditions => ["company_id = ? AND id NOT IN
(?)",@company.id, saved_ids]).each do |association|
      association.destroy
    end
  end

This all works, as long as the user input is correct. But lets state,
that there is a wrong user input in the association data. This will
reside in an application crash, becouse the error is not caught. The
CompanyRole data doesn't get validated and displayed if an error occurs.

The second problem lies in the create method.

@company.save && save_user_role_associations

Here the @company gets save, even if their is an error in the
save_user_role_associations action. How can I handle this?

Then another question (well, 2 question that relate to the same
problem). Lets say I have related models that I need to use. Like a
company can have a role. The roles are shown in a select, but ofcourse
we need to load the resources before we can fill them.
What I do now is create a private action in my controller that pulls all
related resources that are needed.

  def relational_models
    @roles = Role.find(:all)
  end

So, for those resources to be available in the view, I call the
relational_models action in my new / edit action. That works like a
charm. But, the problem occurs when there is an error on the form
submit. As you can see in my above example, I redirect to the new
action. But then the related resources don't get loaded again, and that
resides in an application crash. How can I make sure, the related
resources are loaded again properly?
The 2nd question relates to this problem. I always load custom
css/javascripts dynamically. So only when they are needed. I group my
javascript per controller.
So in overwrite the initialize action in my controller:

  def initialize
    super

    add_styles_and_scripts
  end

The add_styles_and_scripts action is again, a private action in my
controller and can look like this:

  def add_styles_and_scripts
    @scripts = ["company.js"]
  end

This works, but again, when their is an error when the user submits
wrong data, the javascripts/css files don't get loaded again. Its like
the initialize action doesnt get executed again.

I hope you, or someone else can help me with those problems, becouse
once they are out, I have a more solid application.

Thank you in advance

Hi Jeff

[snip]

Wow, I don't think I can respond to everything, but here are a couple
of thoughts....

The second problem lies in the create method.

@company.save && save_user_role_associations

Here the @company gets save, even if their is an error in the
save_user_role_associations action. How can I handle this?

Wrap everything in a transaction:

transaction do
  @company.save!
  save_user_role_assocations
   ...
  end
end

If you raise an exception inside the block, AR will roll everything
back for you.

charm. But, the problem occurs when there is an error on the form
submit. As you can see in my above example, I redirect to the new
action. But then the related resources don't get loaded again, and that
resides in an application crash. How can I make sure, the related
resources are loaded again properly?

Don't redirect back to :new, you need to just *render* your form
again. I know, sounds a bit weird, but otherwise you'll lose your
instance variables, including all your validation errors. Just do:

render :action => :new

to redisplay your form.

Jeff

REST with Rails, Oct 4, 2008, Austin, TX:
http://www.purpleworkshops.com/workshops/rest-and-web-services

Hi Jeff,

If you raise an exception inside the block, AR will roll everything
back for you.

The rollback did come to mind, but will that display the errors that I
have set in my model?

Don't redirect back to :new, you need to just *render* your form
again. I know, sounds a bit weird, but otherwise you'll lose your
instance variables, including all your validation errors. Just do:

render :action => :new

to redisplay your form.

This was my bad, I do rerender the form. But still I lose the instance
variables that I have are related + it looks like the initialize action
isn't invoked

Thank you for all your patience

I can't believe that no one ever came across a similar problem.

It seems that nested resources still can cause some problems in Rails.

If anyone knows why my instance variables get lost or why the initialize
action doesn't get invoked, please do tell me.

I'm eager to learn :slight_smile:

Michael Rigart wrote:

I can't believe that no one ever came across a similar problem.

It seems that nested resources still can cause some problems in Rails.

If anyone knows why my instance variables get lost or why the initialize
action doesn't get invoked, please do tell me.

I'm eager to learn :slight_smile:

Ok, so what I have found out. When you invoke render :action => .... it
doens't run the action. So all the related models don't get loaded
again, I need to load them before the render :action statement :slight_smile:

Invoking those actions can do look a bit nasty in the long run. Would it
be a good idea to run them through a filter (like for example the
before_filter :only => [] )?

Now I havent got the time to see if the nested resources give their
error messages. Going tot test this together with the rollbacks :slight_smile:

Also, I find using validates_associated on the models concerned and
then doing a valid? call on the top level object is quite a good way
to get at the error messages and avoid trying to save objects only to
find they have errors, although I still do use a transaction to ensure
integrity.

Tonypm

tonypm wrote:

Also, I find using validates_associated on the models concerned and
then doing a valid? call on the top level object is quite a good way
to get at the error messages and avoid trying to save objects only to
find they have errors, although I still do use a transaction to ensure
integrity.

Tonypm

Hi tonypm,

could you collaborate your statement a bit further? I don't understand
it fully.

Thank you in advance

Michael Rigart wrote:

tonypm wrote:

Also, I find using validates_associated on the models concerned and
then doing a valid? call on the top level object is quite a good way
to get at the error messages and avoid trying to save objects only to
find they have errors, although I still do use a transaction to ensure
integrity.

Tonypm

Hi tonypm,

could you collaborate your statement a bit further? I don't understand
it fully.

Thank you in advance

Ok, so lets say I'm saving data like this:

  def create
    @company = Company.new(params[:company])

    if @company.save && save_user_role_associations
      flash[:message] = l("company_create_response")
      redirect_to(companies_url)
    else
      add_styles_and_scripts
      relational_objects
      render :action => "new"
    end
  end

private

  def save_user_role_associations
    saved_ids = []

    params[:company_user_association].each do |key, item_vals|
      if item_vals[:id].to_i > 0
        association = CompanyRole.find(item_vals[:id])
        association.update_attributes(item_vals)
      else
        association = CompanyRole.new(:company_id => @company.id,
:role_id => item_vals[:role_id], :user_id => item_vals[:user_id])
        association.save
      end

      item_vals.delete :id
      saved_ids << association.id
    end

    CompanyRole.find(:all, :conditions => ["company_id = ? AND id NOT IN
(?)",@company.id, saved_ids]).each do |association|
      association.destroy
    end
  end

How do I implement the transaction in a clean way.

And how do I merge the CompanyRole errors when they occure? At this
moment I display the company errors like this:

<%= error_messages_for 'company', :header_message => l("try_again"),
:message => l("message_company") %>

But I also want to display the CompanyRole errors in the same list as
the company errors.

Ok, what I am suggesting is to build the object and its associated
objects before saving them. Then doing valid? is a good way to get at
the error messages, and have all the objects constructed so you can
render them. Create is a different case because you are trying to
handle associations for an object that has not yet been created (ie.
has no id). I will make a suggestion for Update

def update
    @company = Company.find(params[:id])
    if
@company.save_company_and_roles(params[:company_user_association])
........etc

In the Company model

validates_associated :company_role

def save_company_and_company_roles(roles)
   # first build the objects
   roles.each do |key, item_vals|
      if item_vals[:id].to_i > 0
        CompanyRole.find(item_vals[:id]) = item_vals
      else
        company_roles.build(item_vals)
      end
    end
    return false if !self.valid? # this should mostly avoid the
transaction ever failing
    transaction do
       self.save
       company_roles.each { |r| r.save}
       company_roles.find(:all, :conditions => [ id NOT IN(?)",
saved_ids]).each do |association|
         association.destroy
       end
    end
end

By having this method in the model, using the company scoped finders
tidies the code a bit. I have tweaked your code to reduce it a bit so
I may have borked it, but you should be able to see what I am driving
at. (Some of the self's are not actually needed)

In your view you can now use error_messages_for ['company',
'company_role']. To improve the error messages, I actually go through
the child objects and do add_to_base for the parent errors. This
allows me to include the child id so that the error is more
explicit.

For the create situation, I would start with a similar method to
figure out what you want to do. Once you have that you will probably
find you can do both together. Incidentally, if you do c=Company.new
and then use c.company_roles.build, then you can still do c.valid? to
generate the errors.

Using the transaction, if there is any failure to save, then an
exception will get raised which you may want to handle in the
controller.

I dont see the Railscasts on complex forms mentioned in this thread,
but I take it you have looked at those. Some great ideas in there.
http://railscasts.com/episodes/73-complex-forms-part-1

hope this makes some sort of sense
Tonypm

tonypm wrote:

Ok, what I am suggesting is to build the object and its associated
objects before saving them. Then doing valid? is a good way to get at
the error messages, and have all the objects constructed so you can
render them. Create is a different case because you are trying to
handle associations for an object that has not yet been created (ie.
has no id). I will make a suggestion for Update

def update
    @company = Company.find(params[:id])
    if
@company.save_company_and_roles(params[:company_user_association])
........etc

In the Company model

validates_associated :company_role

def save_company_and_company_roles(roles)
   # first build the objects
   roles.each do |key, item_vals|
      if item_vals[:id].to_i > 0
        CompanyRole.find(item_vals[:id]) = item_vals
      else
        company_roles.build(item_vals)
      end
    end
    return false if !self.valid? # this should mostly avoid the
transaction ever failing
    transaction do
       self.save
       company_roles.each { |r| r.save}
       company_roles.find(:all, :conditions => [ id NOT IN(?)",
saved_ids]).each do |association|
         association.destroy
       end
    end
end

By having this method in the model, using the company scoped finders
tidies the code a bit. I have tweaked your code to reduce it a bit so
I may have borked it, but you should be able to see what I am driving
at. (Some of the self's are not actually needed)

In your view you can now use error_messages_for ['company',
'company_role']. To improve the error messages, I actually go through
the child objects and do add_to_base for the parent errors. This
allows me to include the child id so that the error is more
explicit.

For the create situation, I would start with a similar method to
figure out what you want to do. Once you have that you will probably
find you can do both together. Incidentally, if you do c=Company.new
and then use c.company_roles.build, then you can still do c.valid? to
generate the errors.

Using the transaction, if there is any failure to save, then an
exception will get raised which you may want to handle in the
controller.

I dont see the Railscasts on complex forms mentioned in this thread,
but I take it you have looked at those. Some great ideas in there.
http://railscasts.com/episodes/73-complex-forms-part-1

hope this makes some sort of sense
Tonypm

Thi Tonypm,

thanks for you explenation. I will look in to it this weekend, since I'm
working on another part of the project today and I need to finish the
rewrite by the end of next week.

You example does make sense, and I also found the railcast threads on
complex forms this morning, so I'm definataly going to take a look at
it.

When I come up with a working solution, I'll post my actions here, so it
might help other people in the future if they run into a similar
problem.

Hi Tonypm

I couldn't resist and start working on it now. And I think I'm almost
there.
For good testing perposes I've implemented the algoritm in a my
invoices, witch has many invoice_items. The filosophie is the same with
companies and roles.

So in my invoice model, I've added:

.....
has_many :invoice_items
validates_associated :invoice_items

  def save_outgoing_invoice_and_items(items)
    saved_ids = []

    return false unless self.valid?

    transaction do
      self.save

      items.each do |key, item_vals|
        if item_vals[:id].to_i > 0
          item = OutgoingInvoiceItem.find(item_vals[:id])
          item.attributes = item_vals
        else
          item = OutgoingInvoiceItem.new(item_vals)
        end
        item.save

        saved_ids << item.id
      end

      outgoing_invoice_items.find(:all, :conditions => ["id NOT IN (?)",
saved_ids]).each do |association|
        association.destroy
      end
    end
  end
.....

Then in the invoice controller, I call the model action to save the
invoice and the items:

   if
@outgoing_invoice.save_outgoing_invoice_and_items(params[:outgoing_invoice_item])

Then in my view I have
<%= error_messages_for ['invoice', 'invoice_items'], :header_message =>
l("try_again"), :message => l("message_invoice") %>

To display the error messages.

The thing now is, that when I make a mistake in the invoice_items, the
invoice gets saved without any invoice_items, and I don't get any error
messages saying that something went wrong. So the transaction doesn't
rollback the parent entry, only the child entries + the fact that I miss
some error reporting.

I can feel it will be something stupid that I overlooked.

PS: The way the model action is now written, it can be used for both
create and update

I think the problem is that you are not building the object before you
do the valid? test. Which means validation will pass, but then in the
transaction, validation will fail so that the invoice_item will not
get saved, but the transaction will not fail because a validation
error is soft. It doesn't raise an exception.

If you want to break the transaction you would need to do a raise if
the save is false. But then you would need to handle the exception in
the controller - and of course subsequent items would not get saved or
loaded into the objects so you would not get their values re-
displayed.

That is why in my example, I build the objects first, then do the
valid? check and then open the transaction and do the saves.

So the valid? is there to trap normal user errors, and the transaction
is there to trap unexpected system errors. Which means that if for
some reason a save fails unexpectedly, the database is not left in an
possible inconsistent state.

hth
Tonypm