Search code examples
ruby-on-railsrubycontrollernomethoderrorupdatemodel

Rails NoMethodError undefined method `data' for nil:NilClass (Controller#update)


Edit: it turns out I made a very simple mistake and had a Template that was associated with a LocalTemplate id that no longer existed. If anyone has this problem and thinks that they somehow are unable to unable to associate the id of another model in their update action, make sure that you didn't accidentally delete the parent object causing that id to no longer exist! The code below, while dramatically simplified did work for me.

I have a Template model in my rails app. It has a method "data" defined in it.

I am able to access this method in the create and show actions with @template.data, however when using the same @template.data in the update action of my controller I get a no method error because I am not showing the correct local template id to it. This line can be found in the model where it reads base_data = YAML.load(local_template.data)

I stored an id of the associated local_template when initially saving a new template, but how can I make sure I reference that id again in the update action so I do not get a no method error?

Here is a simplified version of the Template model and controller

Model:

    class Template < ActiveRecord::Base
      def data
        base_data = YAML.load(local_template.data)
        # couldn't pass the correct LocalTemplate here because
        # the local_template_id I had in my Template model no
        # longer existed. Changing the id to a LocalTemplate
        # that did exist fixed the issue.
      end
    end

Controller:

    class TemplatesController < ApplicationController
      def index
        @business = Business.find(params[:business_id])
        @templates = @business.templates.all
      end

      def new
        @business = Business.find(params[:business_id])
        @local_templates = LocalTemplate.all
        @template = @business.templates.build
      end

      def create
        @business = Business.find(params[:business_id])
        @local_templates = LocalTemplate.all
        @template = @business.templates.build(template_params)

        if @template.save
          @template.data #works fine here

          redirect_to business_url(@template.business_id)
        else
          render 'new'
        end
      end

      def show
        @business = Business.find(params[:business_id])
        @template = @business.templates.find(params[:id])
        @template.data #works fine here too
      end

      def edit
        @business = Business.find(params[:business_id])
        @local_templates = LocalTemplate.all
        @template = @business.templates.find(params[:id])
      end

      def update
       @business = Business.find(params[:business_id])
       @template = @business.templates.find(params[:id])

        if @template.update_attributes!(pass_template_params)

          Api.new.update_template(@template.data.to_json) #this is where I had a problem

          redirect_to business_url(@template.business_id)
        else
          render 'edit'
        end
      end
    end

Solution

  • You are mixing a lot. There is a lot to refactor in your controller...
    First of all, your TemplatesController should be about the template resources, but your controller looks more like a BusinessesController. In general your update action for example should look more like:

    def update
      @template = Template.find params[:id]
      @template.attributes = template_params # though this should raise a NoMethodError, because you dind't define it; I'd prefer params[:template] if possible
      if @template.save
        redirect_to business_url(@template.business_id)
      else
        @local_templates = LocalTemplate.all
        render 'edit'
      end
    end
    

    Instantiating @business and @local_templates makes non sense, because you don't use it at all. Speed up your responses if you can! :)
    Fixed that, there is no need for the overhead of a nested resource in update (as you did).
    If saving @template fails for validation reasons, you better should load the business object late by:

    @template.business
    

    in your /templates/edit.html.erb partial. Then you also do not need a nested route to your edit action... You see, it cleans up a lot.
    As a general guideline you should create as less as possible controller instance variables.
    If you cleaned up your controller and views, debugging your data issue will be easier.
    I assume:

    local_template
    

    in your Template model to be an associated LocalTemplate model object. So it should no issue to call that anywhere if you ensured the referenced object exists:

    class Template < ActiveRecord::Base
      def data
        return if local_template.nil?
        YAML.load(local_template.data)
      end
    end
    

    or validate the existence of the local_template object. or even b