Mongoid relationships

Hello, I am new to Rails and I’m building an API.

I have 2 objects:
Team: name, leader (user object, person who manages the team), users (array of user objects for people in the team (not including the leader))
User: id, name, email, typical fields

Currently my front end sends back a team object as name, users (array of user ids), leader (user id).

My Create and Update look like this for team:

  def update
    @team = Team.find(params[:id])

    if @team.update_attributes!(accepted_params)
      render json: @team
    else
      render json: @team.errors, status: :unprocessable_entity
    end
  end

My Team model looks like this:

class Team
  include Mongoid::Document
  include Mongoid::Timestamps::Created
  include Mongoid::Timestamps::Updated

  # standard
  field :name
  # relationships
  has_one :leader, class_name: 'User'
  has_many :users, inverse_of: :team

  # define the users based on an array of people with the team id
  def users=(user_ids)
    self.users.clear
    user_ids.each do |id|
      User.find(id).update_attribute :team_id, self.id
    end
    self.users
  end
end

My User model looks like this:

class User
  include Mongoid::Document
  include Mongoid::Timestamps::Created
  include Mongoid::Timestamps::Updated
  
  field :name
  ## Database authenticatable
  field :email,              :type => String, :default => ""

  # users belong to a team
  belongs_to :team, inverse_of: :user

end

I have a team serialiser like this:

class TeamSerializer < ActiveModel::Serializer
  attributes :_id, :name
  has_many :users
  has_one :leader
end

The problem I have is that when I save the team (or create it), the leader object never gets saved, it seems to default leader to the first item in the users array.

Can anyone help me get the relationships right? Many thanks for any assistance.

Hi,

Is it just the user_id of the Leader you are wanting to set when saving the Team?
If so, then you just need to make sure that attribute is in your accepted_params and is getting passed through in the request.

If you are wanting to update properties of the child record(User name, email) then you’ll need to save that user explicitly, updating the attributes of the Team in your action won’t update the children’s attributes by default.

The traditional way of doing this is updating both records.

if @leader.update_attributes!(accepted_leader_params)
end
if @team.update_attributes!(accepted_team_params)
end

Rails does have accepts_nested_attributes_for for being able to save child attributes directly on the parent - but most people tend to stay away from that also.

Haven’t used mongoid in a while but another option may be to use their embedded object feature.

class Team
  embeds_one :leader, class_name: 'User'
end

This has it’s drawbacks though - you could never search for this leader outside of the team. It won’t exist as a User document, only as part of the team. I don’t think that’s what you would want to do.

My ideal team object would be:

{

  • name: “Brands Team”,
  • leader:
    {
    [LIST]
  • name: “aaron”,
  • email: aaron@tb.com
    [/LIST]
    },
  • users:
    [
    [LIST]
  • {
    [LIST]
  • name: “aaron”,
  • email: aaron@tb.com
    [/LIST]
    },
  • {
    [LIST]
  • name: “aaron”,
  • email: aaron@tb.com
    [/LIST]
    }
    [/LIST]
    ]

[COLOR=#000000][FONT=monospace]}

I would prefer to use embedding because then I can query the team by its leader.

I stripped everything back and now the only relationships stored are in my team model:
[/FONT][/COLOR]

embeds_one :user, inverse_of: :leader, class_name: 'User'
  embeds_many :users, inverse_of: :users, class_name: 'User'

However when I try and save the team I get this:

Started PUT "/teams/528dec4854eadc680c000001?authentication_token=zCutUnHa5pZUWVN34yLU" for 127.0.0.1 at 2013-11-21 11:21:57 +0000
Processing by TeamsController#update as JSON
  Parameters: {"id"=&gt;"528dec4854eadc680c000001", "name"=&gt;"Brands", "holiday_days_per_year"=&gt;20, "hours_per_day"=&gt;8, "users"=&gt;["528dec4454eadc4677000003"], "leader"=&gt;"528dec4454eadc4677000003", "_id"=&gt;"528dec4854eadc680c000001", "created_at"=&gt;"2013-11-21T11:19:36.285Z", "description"=&gt;nil, "organisation_id"=&gt;1, "role"=&gt;"leader", "slug"=&gt;"brands", "updated_at"=&gt;"2013-11-21T11:19:36.285Z", "authentication_token"=&gt;"zCutUnHa5pZUWVN34yLU"}
  MOPED: 127.0.0.1:27017 QUERY        database=team_builder_dev collection=users selector={"$query"=&gt;{"authentication_token"=&gt;"zCutUnHa5pZUWVN34yLU"}, "$orderby"=&gt;{:_id=&gt;1}} flags=[:slave_ok] limit=-1 skip=0 batch_size=nil fields=nil (0.6428ms)
  MOPED: 127.0.0.1:27017 QUERY        database=team_builder_dev collection=users selector={"$query"=&gt;{"authentication_token"=&gt;"zCutUnHa5pZUWVN34yLU"}, "$orderby"=&gt;{:_id=&gt;1}} flags=[:slave_ok] limit=-1 skip=0 batch_size=nil fields=nil (0.5119ms)
Can't verify CSRF token authenticity
  MOPED: 127.0.0.1:27017 QUERY        database=team_builder_dev collection=teams selector={"_id"=&gt;"528dec4854eadc680c000001"} flags=[:slave_ok] limit=0 skip=0 batch_size=nil fields=nil (0.3591ms)
Unpermitted parameters: id, _id, created_at, description, organisation_id, role, slug, updated_at, authentication_token
Completed 500 Internal Server Error in 5ms


NoMethodError (undefined method `metadata' for "528dec4454eadc4677000003":String):
  app/controllers/teams_controller.rb:41:in `update'




  Rendered /Users/azz0r/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.7ms)
  Rendered /Users/azz0r/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
  Rendered /Users/azz0r/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (0.9ms)
  Rendered /Users/azz0r/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (11.9ms)

Teams controller line 41:

def update
    @team = Team.find(params[:id])


    if @team.update_attributes!(accepted_params)
      render json: @team
    else
      render json: @team.errors, status: :unprocessable_entity
    end
  end

Hi,

I don’t think you want to have everything embedded.

Would you like to be able to do things like User.where(name: “aaron”) ?
If you go down this path you’ll need to search through all teams just to find which one has the user you are looking for.

I would prefer to use embedding because then I can query the team by its leader.

No, you would just need to write something like @aarons_team = Team.where(leader_id: @aaron.id)

The error you’re getting is because the leader attribute being passed through in the request is just the id “528dec4454eadc4677000003”, you’re then trying to save the leader object with just that string.

If you’re still learning Rails I’d suggest steering clear of Mongoid altogether, for some things it makes sense but most times it just complicates a perfectly good simpler alternative. All of the guides are specific to relational db’s so you’re best learning Rails using something like Postgres.