Home > Net >  What is the best way to loop through a collection of records and pass back an object to the front en
What is the best way to loop through a collection of records and pass back an object to the front en

Time:02-20

I have a controller that returns user reports, and one of the methods sums up the points of said reports, per user. I want to pass back an object of this data to the front end so it can be displayed. Ideally my object would be shaped like this:

data: {
  users: {
    $user_id: {
      name: "Foo Bar",
      points: 100
    },
    $user_id: {
      name: "Foo Bar Two",
      points: 10
    }
  }
}

However my current implementation is not building the object like this, and simply adding to one big object.

My code looks like this:

  def user_points
    hash = {}
    User.all.each do |u|
      user_points = Report.select("points").where("user_id = ?", u.id).sum("points")
      hash.merge!(
        user: 
        {
          first_name: u.first_name,
          last_name:u.last_name,
          time_zone: u.time_zone
        }
      )
    end
    render json: { data: hash }
  end

and the resulting object only included the last user in one big object

data:
  user:
  first_name: "Test"
  last_name: "Test"
  points: 200
  time_zone: "Pacific Time (US & Canada)"

CodePudding user response:

As mentioned by dbugger you need to provide a unique key for each hash entry otherwise merge will just replace an existing value.

For example:

{a: :foo}.merge(b: :bar)
=> {:a=>:foo, :b=>:bar} 

and

{a: :foo}.merge(b: :bar).merge(a: :foo_bar)
{:a=>:foo_bar, :b=>:bar}

You might want to consider returning a json array rather than an object with unique property names.

maybe something like this?

  def user_points
    result = User.all.map do |u|
      points = Report.select("points").where("user_id = ?", u.id).sum("points")
        {
          first_name: u.first_name,
          last_name:u.last_name,
          time_zone: u.time_zone
          points: points
        }
    end
    render json: { data: result }
  end

CodePudding user response:

You can also achieve the same result by joining both the table and then performing aggregation on joined table.

select users.id, users.name, sum(reports.points) as points from users join reports on users.id = reports.user_id group by users.id;

sql-fiddle

def user_points
  result = User.join(:reports)
            .select("users.first_name, users.last_name, SUM(reports.points) AS points, users.time_zone")
            .group("users.id")
  render json: { data: result }
end

Output:

data:
  first_name: "Test1"
  last_name: "Test1"
  points: 100
  time_zone: "Pacific Time (US & Canada)"

  first_name: "Test2"
  last_name: "Test2"
  points: 200
  time_zone: "Pacific Time (Asia)"

  first_name: "Test3"
  last_name: "Test3"
  points: 300
  time_zone: "Pacific Time (Europe)"
  • Related