kli216
kli216

Reputation: 75

STI with a Single Controller

I have several models (User, Goal) and my Goal model has several subtypes (Exercise, Yoga, etc.)

A user can have many goals, but only one of each type.

class User < ActiveRecord::Base
    has_many :goals, dependent: :destroy
    has_one :exercise
    has_one :water
    has_one :yoga

    def set_default_role
        self.role ||= :user
    end
end

and

class Goal < ActiveRecord::Base
    self.inheritance_column = :description

    belongs_to :user
    validates :user_id, presence: true
    validates :description, presence: true
end

where a subclass of goal is just something like this

class Exercise < Goal
    belongs_to :user
end

I want to create all types of goals in my goal controller and have it set up so that localhostL:3000/waters/new will be my "water goal-type" creation page. I'm having trouble correctly setting it up though so that description is automatically set (my STI column) because I also want to build it through my user (so user_id is also set).

my goals controller currently looks like this...

def create
   @goal =  current_user.goals.build
   ???? code to set goal.description = subtype that the request is coming from

   respond_to do |format|
      if @goal.save
        format.html { redirect_to @goal, notice: 'Goal was successfully created.' }
        format.json { render action: 'show', status: :created, location: @goal }
      else
        format.html { render action: 'new' }
        format.json { render json: @goal.errors, status: :unprocessable_entity }
      end
    end
end

I'm pretty new to rails so a little bit confused. Also using Rails 4.0

Upvotes: 1

Views: 239

Answers (1)

fabianfetik
fabianfetik

Reputation: 774

In that case you'd need to communicate the type to create to the controller somehow.

This is most easily established by including a hidden field in the form which has a value of the type you want to create.

Assuming your block variable for the form is f

<%= f.hidden_field :type, :value => "Excercise" %>

Then you can build a goal like this:

current_user.goals.build(type: params[:type])

You can easily see how this can be very dangerous, you should always be doubly careful when using user submitted data.

To guard against a malicious user, you could set a constant in your controller

AcceptedModels = ["Excercise", "Yoga", "Water"]

def accepted_type
  (AcceptedModels & params[:type]).first
end

If you also want to hide your internal structure, you could use a hash and send any identifier

AcceptedModels = {"0": "Excercise", "1": "Yoga", "2": "Water"}

def accepted_type
  AcceptedModels.fetch(params[:type], nil)
end

in either case you can then build your goal like this:

current_user.goals.build(type: accepted_type)

The biggest downside with this is that you will have to keep the AcceptedModels updated whenever you add/remove more goals

Upvotes: 1

Related Questions