Rails中如何通过单个表单创建两个属性相同的关联模型?
Great question! The best practice here is to handle this initialization logic in your model layer—here's why, plus how to implement it cleanly:
Why Model Layer?
Putting this logic in the model aligns with Rails' core principles:
- Single Responsibility: Controllers should focus on handling HTTP requests/responses, not business logic like copying data between related models.
- Reusability: If you ever need to create a
Goalsinstance from aWaterusageelsewhere (e.g., a background job, admin tool), you can reuse the same method instead of duplicating code. - Testability: Model methods are far easier to test in isolation, without needing to mock HTTP requests or controller contexts.
- Cleaner Code: Encapsulating the copy logic keeps your controller lean and focused on request flow.
Implementation Steps
1. Add a Factory Method to the Goals Model
Create a class method in Goals that takes a Waterusage instance and returns a pre-populated Goals instance:
class Goals < ApplicationRecord belongs_to :user # Keep all your existing calculation methods (same as Waterusage) # Class method to build a Goals instance from a Waterusage def self.from_waterusage(waterusage) # Copy all attributes except auto-generated fields and user_id (we'll set that separately) copied_attributes = waterusage.attributes.except('id', 'created_at', 'updated_at', 'user_id') # Build the new Goals instance, linking to the same user new(copied_attributes.merge(user: waterusage.user)) end end
2. Trigger Creation (Two Options)
Option A: Automatic Creation via Waterusage Callback
If you want the Goals instance to be created automatically whenever a Waterusage is saved, add an after_create callback to Waterusage:
class Waterusage < ApplicationRecord belongs_to :user before_validation :calculate_totals after_create :create_associated_goals # Keep all your existing calculation methods... private def create_associated_goals # Use our factory method and save the Goals instance Goals.from_waterusage(self).save! end end
This ensures every new Waterusage gets a matching Goals record without extra controller code.
Option B: Explicit Creation in the Controller
If you need more control (e.g., only create Goals under certain conditions), call the factory method directly in your WaterusagesController:
class WaterusagesController < ApplicationController def create @waterusage = current_user.waterusages.build(waterusage_params) if @waterusage.save # Build and save the Goals instance @goal = Goals.from_waterusage(@waterusage) if @goal.save redirect_to your_success_path, notice: 'Water usage and goals created successfully!' else flash[:alert] = 'Water usage saved, but we couldn’t create your goals. Please try again.' redirect_to your_fallback_path end else render :new end end # Your existing waterusage_params method... end
Ensuring Independent Editing
Since Waterusage and Goals are separate models with their own database records, editing Goals attributes later won’t affect the original Waterusage data—this is already handled by Rails’ default behavior. Just make sure your GoalsController has standard edit/update actions to let users modify their goals.
内容的提问来源于stack exchange,提问作者ezweizig

