AddRow should return the row just added, not the "action"

Given that the default formula for Activating a row after adding it is

Activate(AddRow([My Table]))

You would think that AddRow returns the row that was just added. However, that seems not to be the case, rather it seems that it returns the action. So, this works as you would expect:

WithName(AddRow([My Table]), deal, Activate(deal))

But,

WithName(AddRow([My Table]), deal, 
  AddRow([Other Table], [Other Table].[Creation Date], deal.[Creation Date])
)

Does not work.

I’m trying to get the Url of the new row so that I can open it with a specific layout.

4 Likes

Current workaround for this issue is to grab the most recently created row from the table. But that’s not an ideal solution due to race conditions.

2 Likes

+1 – running into this now and it’s quite annoying, especially since automations have the ability to grab the newly created row (so there’s already a mechanism for this)

Is this something the Coda team plans on changing? It seems like returning the row is expected behavior.

2 Likes

This bug seems to be partially solved. This works now:

AddRow(...).Activate()

But there is an issue still.

The following code will create two rows in Table 1 and two in Table 2. The expected behaviour is to end up with only one row in Table 1, and two rows in Table 2 that have a reference to the new row in Table 1.

WithName(
  [Table 1].AddRow([Table 1].Name, "Orignal row"),
  NewRow,
  List("Reference row 1", "Reference row 2")
    .ForEach(
      [Table 2]
        .AddRow(
          [Table 2].Name, CurrentValue, [Table 2].Ref, NewRow
        )
    )
)

What happens:
Screenshot 2022-11-28 at 15.04.15

What should happen:
Screenshot 2022-11-28 at 15.04.24

A simple example for this use case is to create a Project with multiple Tasks when clicking a button.

The WithName “NewRow” contains a function that you are calling twice (in your ForEach() section.

The following will work (although I would set it up different, but perhaps you simplified your example only for the community):

WithName("original row",NewRow,
WithName(list("task 1","task 2","task 3"),tasks,
  RunActions(
    AddRow(TableA,TableA.Project,NewRow),
    tasks.ForEach(
      AddRow(TableB,
        TableB.project,NewRow,
        TableB.task,CurrentValue
       )
     )
   )
  )
  )

image

Thank you for trying to help @joost_mineur, but that is the same workaround others have suggested, only in your case instead of finding the row after creating it, you set the text value of “Project” instead of using a reference to the row. To see why this is not an ideal workaround you should just try running your code twice with the same project name (which might happen in reality), and you get two projects, 6 tasks, and all tasks are assigned to the first project.


Ideally “AddRow” should return the reference to the newly created row, or if for legacy reasons it needs to return the function, that function should at least return the row and not another function.

What currently happens:
AddRow(…) → FunctionToCreateRow() → FunctionToCreateRow() → to infinity…

What should happen:
AddRow(…) → CreatedRow

If that is not possible because the API shouldn’t change, then this:
AddRow(…) → FunctionToCreateRow() → CreatedRow

1 Like

I guess I misunderstood what you were trying to accomplish. It was not meant as a work around, but in your example you had “original row”, which is not an object and is not going to be an object unless you somehow make it an an object.

I can’t find a good way to do this without using a workaround - have some hidden columns (the display columns) and create the objects with a formula in the table.

The following is possible (I added the date/time to the project name to make it easier to distinguish the different projects):

Although I don’t think I would use this construct myself the way, it does what you want it to do.

The one part I don’t like, is the part where I use last() in the button code:

    WithName(list("task 1","task 2","task 3"),tasks,
      RunActions(
        AddRow(TableA,TableA.Project,"New Project: "+now().ToText()),
        WithName(TableA.ProjectObject.last(),ProjectObject,
        tasks.ForEach(
        AddRow(TableB,
          TableB.task,CurrentValue,
          TableB.project,ProjectObject)
         )
       )
     )
   )

In a very active multi-user environment this could potentially create a mismatch. I am not sure if there is a better solution, maybe @Paul_Danyliuk has a better way to capture an object.

It’s easier to understand the above if you look at a demo doc: Object connected tables

in your example you had “original row”, which is not an object and is not going to be an object unless you somehow make it an an object.

It becomes an object if you select the proper column type. Coda is smart enough to find the row based on the name.
Screenshot 2022-11-29 at 10.45.59
That is why your original code (at least partially worked). If you use a reference column these two things are the same:

TableB.AddRow(TableB.project, "orignal row")

and

TableB.AddRow(TableB.project, TableA.Filter(Project="original row").First())

Unfortunately as most of us have seen, this approach has downsides because two rows can have the same data and it can be impossible to differentiate them without extra columns (as you rightfully suggested).

Thank you for trying to help and offer solutions @joost_mineur, but in my current use case I have enough data where I can reliably differentiate rows but that is not always the case. The point of my post was not the find a different solution/workaround, it was to give more visibility to this suggestion so hopefully the Coda team can change the way AddRow works, or add another function that behaves in the way we expect.

@Paul_Danyliuk
Please, help us :smile:
The main point here is that for consecutive actions it would be great if we could reference a previously created row while creating child rows all in the same formula, for example

1 Like

Hi Daniel,

I unfortunately do not have time now to explain on this topic, but below is some explanations that I had done earlier for similar questions.

Regards
Piet

1 Like

I created a system where I have a hidden relation control set to “personal”. The action that creates the row also sets this hidden relation control to the newly created row. Then in additional actions, I use the relation control to identify the “new” record.

image

4 Likes

This is very helpful, thanks!!

Fascinating. I must test this approach.

However, it requires an extra control-value for each table - and those must be set to personal.
My clients may change that without realizing the importance.
This occurs when my clients add automations; those automations need control-values to be collaborative. So folks have changed this in the past withour realizing how it changes the behavours in other parts of the workflow.

Instead, I have used an alternative approach that does not require extra control-values but still avoids race conditions (among several users).

If you simply use Table1.Last() to select the row you just inserted, it will fail if another user has also just inserted a row. Each of you may or may-not get the correct row - the classic RACE condition.

So instead, we select the Last() row that was CreatedBy() this User().

However the formula to do that is
Table1.Filter(CreatedBy(CurrentValue)=User()).Last()
which can be very slow if Table1 has many rows (Filter() has to hit every row in the table).

So instead we first check that the Last() row was CreatedBy() this User(), if it was, then we use that and avoid having to run the Filter(). This is the most frequent case, and it runs very fast.

But if the Last() row was not CreatedBy() this User(), then we must do the whole Filter(CreatedBy(CurrentValue)=User()).Last() process. This is slower, but it avoids the race condition, and always gives us the correct row.

Here is an example where we create a new row in the PROJECTS table and then get a link to that row, called newProj, which we then use to create a row in the TASKS table for that Project.

RunActions(
    PROJECTS.AddRow(                           // create new project
        Name, "Test Project 1",
        Owner, "max.xyzor@agile-dynamics.com"
    ),
    If( PROJECTS.Last().CreatedBy() = User(),  // no race-condition?
        PROJECTS.Last(),                       // use last row in table
                                               // otherwise we need to filter
        PROJECTS.Filter(CreatedBy(CurrentValue)=User()).Last()
    ).WithName(newProject),                    // call the link "newProject"
    TASKS.AddRow(
        Task, "Initiate New Project",          // add a new task
        Project, newProject,                   // for this project
        DueDate, Today()+7
    )
)

Max

5 Likes

Another approach would be to add a Unique ID column and generate a GUID on the initial insert. Using the same GUID you generated would give you 100% certainty on the project.

(Requires @Vinny_Green1’s UUID pack: UUID Pack, extend Coda with UUID - Coda)

WithName(
  UUID::UUID(4),
  Guid,
  RunActions(
    AddRow(
      Projects, Projects.Name, "My Project", Projects.[Unique ID], Guid
    ),
    Projects.filter([Unique ID] = Guid).First()
      .WithName(
        NewProject, AddRow(Tasks, Tasks.Project, NewProject, Name, "New Task")
      )
  )
)
2 Likes

@Daniel_Velho so sorry I overlooked the ping almost a year ago, and thank heavens this thread got bumped recently. I just needed a topic like this exactly to make a simple intermezzo video for today.

Here’s my ultimate answer:

2 Likes

this is a brilliant solution! wow…