This question already has answers here:
strong parameters permit all attributes for nested attributes
(6 answers)
Closed 7 years ago.
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)
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
I have searched this extensively and also looked at unofficial Vine API. Below is the code I see in endpoints file :
def post(videoUrl, thumbnailUrl, description, entities, optionals = {} )
forsquareVenueId = optionals["forsquareVenueId"] || optionals[:forsquareVenueId]; venueName = optionals["venueName"] || optionals[:venueName]; channelId = optionals["channelId"] || optionals[:channelId]
url = (API_URL + "posts") % []
params = { "forsquareVenueId" => forsquareVenueId , "venueName" => venueName , "channelId" => channelId , "videoUrl" => videoUrl , "thumbnailUrl" => thumbnailUrl , "description" => description , "entities" => entities }.reject { |k, v| v.nil? }
api_call "post", url, params, nil
end
From what I have understood, it seems I need to first create a post, then update it using HTTP Put method with video data and thumbnail data. But I am not clear how to populate various fields such as 'entities' in JSON. Need some help on this.
In a rails project, I have 1 controller and 2 model. I want send 1 request from angularjs to rails server and for response, get 2 json array, 1. first model. 2. seccond model.
Now I use below code, and get just 1 of 2 array:
Rails Contorller: tables_controller.rb:
class Api::V1::TablesController < Api::V1::BaseController
def index
#table = Table.all
#mostagheltype = Mostagheltype.all
//I can just send 1 of model.
respond_with(Table.all)
end
end
Angularjs Controller: table.js:
$scope.tables = Tables.index();
tableService.js:
'use strict';
var app = angular.module('tableService', ['ngResource']);
app.factory('Tables', function($resource) {
return $resource('/api/tables.json', {}, {
index: { method: 'GET', isArray: true}
});
});
I can push 2 table in 1 array in rails controller and then recieve this from angular controller, like below:
array = []
Table.all.each do |table|
array << { name: table.name, check: 1 }
end
Mostagheltype.all.each do |m|
array << { name: mostagheltype.name, check: 2}
end
//I can seprate 2 array by `check` value in angularjs part.
but I want a solution that I send each array separate. How can I do this? Any idea?
Short answer no. You can't have two responses to one request. You can respond with an object that contains both arrays.
My is a little rusty. So this may be more psuedo code than ruby;
obj = {}
obj.array1 =[]
obj.array2 =[]
Then populate each array and return the object.
In my rails controller I have something like this:
def index
provider_id = params[:provider] ||= 'all'
thera_class = params[:therapeutic_class] ||= 'all'
med_name = params[:medication_name] ||= 'all'
In the JavaScript side I am passing these params as URL query params so when it gets called it goes to this Index action method:
window.open("http://localhost:3000/pharmacy/patients?provider="+provider_id+"&"+"therapeutic_class="+thera_class+"&"+"medication_name="+medication_name);
The problem is the JavaScript values I am passing if they don't have a value and are undefined they will be passed as undefined.
I am more concerned to know is it too much architecturally wrong that I am doing this? What is the "Rails way" of doing it? Specially the routing from Javascript to Rails controller and passing params to it is where I need your architectural input.
When you fetch your infos on the Client side with Javascript, I guess you initialize the variables provider_id, therapeutic_class, etc. Something like this:
var provider_id = $('#provider').val();
var thera_class = $('#therapeutic_class').val();
# etc.
# and then you do:
window.open("http://localhost:3000/pharmacy/patients?provider="+provider_id+"&"+"therapeutic_class="+thera_class+"&"+"medication_name="+medication_name);
If yes, I would suggest you to do so:
var url = "http://localhost:3000/pharmacy/patients?";
var params = { };
var params_names = [ 'provider', 'therapeutic_class', 'medication_name' ];
$.each(params_names, function(i, param_name) {
var value = $('#'+param_name).val();
if(value !== undefined && value != '') {
params[param_name] = value;
}
})
$.each(params, function(key, value) {
url += key + "=" + value + "&";
})
window.open(url)
Hi I have a JSON string that looks like this (Usingt Rails and a REST service)
{
person:
{
name:"Pepe",
last:"Smith"
hats:[ { team:"lakers", color:"purple"}, { team:"heats", color:"red" }] } }
I want to be able to able to get that JSON, and save the Person to the database, but I want to save the "hats".. as a string to the database; without parsing it or anything like that
i.e. I want to save this to SQL:
hats = "[ { team:"lakers", color:"purple"}, { team:"heats", color:"red" }] }"
Is there a way to do this in rails?
The JSON string gets converted to params hash before a controller action is invoked.
You could make to_json call on the hats attribute to get an equivalent json string.
def create
params[:person][:hats] = (params[:person][:hats]||{}).to_json
p = Person.new(params[:person])
if p.save
#success
else
#error
end
end