2

I'm working on an AngularJS and Rails app that uses Google's definition API. The API returns a JSON response.

I would like to store the JSON response in my Words table. I'm issuing the API request through my Angular controller, then I'm trying to add the data to my Words table.

I'm getting an "Unpermitted parameters" error when I try to insert the JSON data. I've researched and this seems to be due to Rails 4 nested params.

I've tried adding nested brackets to my word_params method in Words controller but then get "Unpermitted parameters: dataType, numToShow, groupNumber, groupResult, sectionType, dictionary"... which is part of the API response.

Is there an easier way to permit the full JSON data without having to go through and specifically define each nested param in the controller's word_params method?

scheme.rb

create_table "words", force: :cascade do |t|
  t.string   "title"
  t.json     "full_data"
  t.integer  "list_id"
  t.datetime "date"
end

Word controller

class WordsController < ApplicationController
respond_to :json

def create
  list = List.find(params[:list_id])
  word = list.words.create(word_params)
  respond_with list, word
end

private
def word_params
  params.require(:word).permit(:title, :date, :full_data => {})
end

end

app.js

//= require angular-rails-templates
//= require_tree .

angular.module('d-angular', ['ui.router', 'templates'])

// Set routing/configuration
// ------------------------------
.config(['$stateProvider', '$urlRouterProvider',

    // Set state providers
    function($stateProvider, $urlRouterProvider) {$stateProvider

        // Home state
        .state('home', {
          url: '/home',
          templateUrl: 'home.html',
          controller: 'MainCtrl',
          resolve: {
              listPromise: ['lists', function(lists){
                return lists.getAll();
              }]
          }
        })

        // Lists state
        .state('lists', {
          url: '/lists/{id}',
          templateUrl: 'list.html',
          controller: 'ListsCtrl',
            resolve: {
              list: ['$stateParams', 'lists', function($stateParams, lists) {
                return lists.get($stateParams.id);
              }]
            }
        })

        $urlRouterProvider.otherwise('home');
    }
])


// lists factory
// Factories are used to organize and share code across the app.
// ------------------------------
.factory('lists', ['$http',

    function($http){
        // create new obect with array of lists
        var o = { lists: [] };

        // get all lists
        o.getAll = function() {
            return $http.get('/lists.json').success(function(data){
                angular.copy(data, o.lists);
            });
        };

        // get specific list
        o.get = function(id) {
          return $http.get('/lists/' + id + '.json').then(function(res){
            return res.data;
          });
        };

        // create list
        o.create = function(post) {
          return $http.post('/lists.json', post).success(function(data){
            o.lists.push(data);
          });
        };

        // add word to list
        o.addWord = function(id, word) {
          return $http.post('/lists/' + id + '/words.json', word);
        };

        return o;

    }
])

// Lists controller
// ------------------------------
.controller('ListsCtrl', ['$scope', 'lists', 'list', '$http',

    // Main scope (used in views)
    function($scope, lists, list, $http){

        $scope.list = list;             // get list by ID

        // Add word function
        $scope.addWord = function(){

            // API URL
            var api_url = "https://www.googleapis.com/scribe/v1/research?key=AIzaSyDqVYORLCUXxSv7zneerIgC2UYMnxvPeqQ&dataset=dictionary&dictionaryLanguage=en&query=";

            // get data from API
            $http.get(api_url + $scope.title)

                // handle successful api request
                .success(function (response) {

                    // push new word to array
                    lists.addWord(list.id, {
                        title: $scope.title,
                        date: new Date().toJSON().slice(0,10),
                        full_data: response
                    })
                    .success(function(word) {
                        $scope.list.words.push(word);
                    });
                });

        };

    }

]);

Console response

Started POST "/lists/1/words.json" for ::1 at 2015-05-08 19:53:11 -0400

    Processing by WordsController#create as JSON
      Parameters: {"title"=>"fallacy", "date"=>"2015-05-08", "full_data"=>{"dataType"=>"dictionary", "numToShow"=>1, "groupNumber"=>0, "groupResult"=>{"query"=>"fallacy", "displayName"=>"<b>fal·la·cy</b>", "dataset"=>{"dataset"=>"dictionary"}, "score"=>1}, "sectionType"=>"dictionary", "dictionary"=>{"word"=>"fallacy", "dictionaryType"=>"STANDARD", "definitionData"=>[{"word"=>"fallacy", "pos"=>"Noun", "meanings"=>[{"meaning"=>"a mistaken belief, especially one based on unsound argument.", "examples"=>["the notion that the camera never lies is a fallacy"], "synonyms"=>[{"nym"=>"misconception", "nymResult"=>{"query"=>"misconception", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"misbelief", "nymResult"=>{"query"=>"misbelief", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"delusion", "nymResult"=>{"query"=>"delusion", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"mistaken impression"}, {"nym"=>"error", "nymResult"=>{"query"=>"error", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"misapprehension", "nymResult"=>{"query"=>"misapprehension", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"misinterpretation"}, {"nym"=>"misconstruction", "nymResult"=>{"query"=>"misconstruction", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"mistake", "nymResult"=>{"query"=>"mistake", "dataset"=>{"dataset"=>"dictionary"}}}], "submeanings"=>[{"meaning"=>"a failure in reasoning that renders an argument invalid."}, {"meaning"=>"faulty reasoning; misleading or unsound argument.", "examples"=>["the potential for fallacy which lies behind the notion of self-esteem"]}]}], "phoneticText"=>"ˈfaləsē", "wordForms"=>[{"word"=>"fallacy", "form"=>"noun"}, {"word"=>"fallacies", "form"=>"plural noun"}]}]}}, "list_id"=>"1", "word"=>{"title"=>"fallacy", "full_data"=>{"dataType"=>"dictionary", "numToShow"=>1, "groupNumber"=>0, "groupResult"=>{"query"=>"fallacy", "displayName"=>"<b>fal·la·cy</b>", "dataset"=>{"dataset"=>"dictionary"}, "score"=>1}, "sectionType"=>"dictionary", "dictionary"=>{"word"=>"fallacy", "dictionaryType"=>"STANDARD", "definitionData"=>[{"word"=>"fallacy", "pos"=>"Noun", "meanings"=>[{"meaning"=>"a mistaken belief, especially one based on unsound argument.", "examples"=>["the notion that the camera never lies is a fallacy"], "synonyms"=>[{"nym"=>"misconception", "nymResult"=>{"query"=>"misconception", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"misbelief", "nymResult"=>{"query"=>"misbelief", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"delusion", "nymResult"=>{"query"=>"delusion", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"mistaken impression"}, {"nym"=>"error", "nymResult"=>{"query"=>"error", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"misapprehension", "nymResult"=>{"query"=>"misapprehension", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"misinterpretation"}, {"nym"=>"misconstruction", "nymResult"=>{"query"=>"misconstruction", "dataset"=>{"dataset"=>"dictionary"}}}, {"nym"=>"mistake", "nymResult"=>{"query"=>"mistake", "dataset"=>{"dataset"=>"dictionary"}}}], "submeanings"=>[{"meaning"=>"a failure in reasoning that renders an argument invalid."}, {"meaning"=>"faulty reasoning; misleading or unsound argument.", "examples"=>["the potential for fallacy which lies behind the notion of self-esteem"]}]}], "phoneticText"=>"ˈfaləsē", "wordForms"=>[{"word"=>"fallacy", "form"=>"noun"}, {"word"=>"fallacies", "form"=>"plural noun"}]}]}}, "date"=>"2015-05-08"}}
      List Load (0.1ms)  SELECT  "lists".* FROM "lists" WHERE "lists"."id" = $1 LIMIT 1  [["id", 1]]

    Unpermitted parameters: dataType, numToShow, groupNumber, groupResult, sectionType, dictionary

       (0.1ms)  BEGIN
      Word Exists (0.2ms)  SELECT  1 AS one FROM "words" WHERE ("words"."title" = 'fallacy' AND "words"."title" = 'fallacy') LIMIT 1
      SQL (0.2ms)  INSERT INTO "words" ("title", "date", "full_data", "list_id") VALUES ($1, $2, $3, $4) RETURNING "id"  [["title", "fallacy"], ["date", "2015-05-08 00:00:00.000000"], ["full_data", "{}"], ["list_id", 1]]
       (0.7ms)  COMMIT
    Completed 201 Created in 8ms (Views: 0.4ms | ActiveRecord: 1.4ms)
Kyle
  • 1,153
  • 5
  • 28
  • 56

1 Answers1

2

Note that if you use permit in a key that points to a hash, it won't allow all the hash. You also need to specify which attributes inside the hash should be whitelisted.

http://edgeapi.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit

Rails does not really provide a straight-forward way to blindly whitelist a nested parameter hash.

You could do something hackey like:

params.require(:word).permit(:full_data).tap do |whitelisted|
  whitelisted[:full_data] = params[:word][:full_data]
end
max
  • 96,212
  • 14
  • 104
  • 165