Here's the Gurobi code to solve the optimization problem:

```python
import gurobipy as gp

# Create a new model
m = gp.Model("optimization_problem")

# Create variables
pasta = m.addVar(vtype=gp.GRB.CONTINUOUS, name="bowls_of_pasta")
beans = m.addVar(vtype=gp.GRB.CONTINUOUS, name="black_beans")

# Set objective function
m.setObjective(3 * pasta + 8 * beans, gp.GRB.MAXIMIZE)

# Add constraints
m.addConstr(5 * pasta + 16 * beans >= 12, "umami_min")
m.addConstr(8 * pasta + 12 * beans >= 11, "iron_min")
m.addConstr(-4 * pasta + 10 * beans >= 0, "pasta_beans_relation")
m.addConstr(5 * pasta + 16 * beans <= 57, "umami_max")
m.addConstr(8 * pasta + 12 * beans <= 27, "iron_max")


# Resource Constraints based on provided dictionary (although these are superseded by the explicit constraints above)
resource_constraints = {
    'r0': {'description': 'umami index', 'upper_bound': 76, 'x0': 5, 'x1': 16},
    'r1': {'description': 'milligrams of iron', 'upper_bound': 52, 'x0': 8, 'x1': 12}
}

# These constraints are already explicitly defined above, so adding them here is redundant.
# However, this demonstrates how to use the resource dictionary to generate constraints.
# for r_key, r_data in resource_constraints.items():
#     m.addConstr(r_data['x0'] * pasta + r_data['x1'] * beans <= r_data['upper_bound'], r_key)


# Optimize model
m.optimize()

# Print results
if m.status == gp.GRB.OPTIMAL:
    print('Optimal solution found:')
    print(f'Bowls of pasta: {pasta.x}')
    print(f'Black beans: {beans.x}')
    print(f'Objective value: {m.objVal}')
elif m.status == gp.GRB.INFEASIBLE:
    print('Model is infeasible.')
else:
    print(f'Optimization ended with status {m.status}')

```
