Rails 4 unpermitted parameters - JSON data [duplicate] - ruby-on-rails

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

Related

Custom filter to active admin to extract multiple rows on Index page

I have a working code that filter out one row of if the conditions are met
ransacker :custome_search, formatter: proc { |v|
user = Util.new(param: v.strip).user. # gets the data from an external service
id = user.present? ? user.id : nil
id = id.presence
} do |parent|
parent.table[:id]
end
and a filter in my index page as
filter :custome_search, label: 'Custom Data Search:', filters: ['equals']
this work fine, but now I have to change this to accommodate multiple tuples
so I changed my code to
ransacker :custome_search, formatter: proc { |v|
users = Util.new(param: v.strip).find_users
ids = users.present? ? users.pluck(:id) : nil # now returns an array instead of just one id [1,2,3]
ids = ids.presence
} do |parent|
parent.table[:id]
end
the above code displays zero results as the query formed internally comes out to be
SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."id" = NULL LIMIT $1 OFFSET $2) subquery_for_count /*controller:users,action:index*/ [["LIMIT", 30], ["OFFSET", 0]]
but if I do a
ids=ids.first
the same will display one row,
how to I change my code to display multiple rows on the index page
I am using active admin 2.0.0
Well the ransack code was fine, I just needed to change the filter on the index page , any of these would work
filter :custome_search, label: 'Custom Data Search:', filters: ['in']
or
filter :custome_search_in, label: 'Custom Data Search:', as: :string

Mongoid Aggregate result into an instance of a rails model

Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.

Merging dynamically generated attributes into a new entry and summing their values

I'm looking for some advice on how to properly merge some key/value pairs into a separate database entry and summing their values.
I have a Task which has a Vendor_Upload which has many Vendor_Shipping_Logs which has many Vendor_Shipping_Log_Products. I'm not sure if the deep nesting makes a difference, but the important values to look at here are the Item_ID and Quantity.
This is currently how the parameters are spit out:
Parameters: {
"task"=>{
"task_type"=>"Vendor Upload",
"vendor_upload_attributes"=>{
"upload_type"=>"Warranty Orders",
"vendor_shipping_logs_attributes"=>{
"1490674883303"=>{
"guest_name"=>"Martin Crane",
"order_number"=>"33101",
"vendor_shipping_log_products_attributes"=>{
"1490675774108"=>{
"item_id"=>"211",
"quantity"=>"3"
},
"1490675775147"=>{
"item_id"=>"213",
"quantity"=>"6"
}
}
},
"1490674884454"=>{
"guest_name"=>"Frasier Crane",
"order_number"=>"33102",
"vendor_shipping_log_products_attributes"=>{
"1490675808026"=>{
"item_id"=>"214",
"quantity"=>"10"
},
"1490675808744"=>{
"item_id"=>"213",
"quantity"=>"1"
}
}
},
"1490674885293"=>{
"guest_name"=>"Niles Crane",
"order_number"=>"33103",
"vendor_shipping_log_products_attributes"=>{
"1490675837184"=>{
"item_id"=>"211",
"quantity"=>"3"
}
}
},
"1490674886373"=>{
"guest_name"=>"Daphne Moon",
"order_number"=>"33104",
"vendor_shipping_log_products_attributes"=>{
"1490675852950"=>{
"item_id"=>"213",
"quantity"=>"8"
},
"1490675853845"=>{
"item_id"=>"214",
"quantity"=>"11"
}
}
}
}
}
}
}
Upon submission I want to merge each unique Vendor_Shipping_Log_Products Item_IDs and sum their quantities into a new Stockmovement_Batch as a nested Stockmovement to keep my inventories up to date.
See example patameters here of what I would like the output to look like:
Parameters: {
"stockmovement_batch"=>{
"stockmovement_type"=>"Ecomm Order",
"stockmovements_attributes"=>{
"1490676054881"=>{
"item_id"=>"211",
"adjust_quantity"=>"-6"
},
"1490676055897"=>{
"item_id"=>"213",
"adjust_quantity"=>"-15"
},
"1490676057616"=>{
"item_id"=>"214",
"adjust_quantity"=>"-21"
}
}
}
}
Is this something I can do all in one simple go, or do I have to stick with doing each process in a separate form?
First you need to separate out the values you want to iterate through:
data = params.require("task")
.require("vendor_upload_attributes")
.require("vendor_shipping_logs_attributes")
Then pull the vendor_shipping_log_products_attributes and flatten it to an array of hashes:
logs = data.values.map do |h|
h["vendor_shipping_log_products_attributes"].values
end.flatten
# => [{"item_id"=>"211", "quantity"=>"3"}, {"item_id"=>"213", "quantity"=>"6"}, {"item_id"=>"214", "quantity"=>"10"}, {"item_id"=>"213", "quantity"=>"1"}, {"item_id"=>"211", "quantity"=>"3"}, {"item_id"=>"213", "quantity"=>"8"}, {"item_id"=>"214", "quantity"=>"11"}]
Then we merge the data by creating a intermediary hash where we use the item_id as keys.
stockmovements = logs.each_with_object({}) do |hash, memo|
id = hash["item_id"]
memo[id] ||= []
memo[id].push(hash["quantity"].to_i)
end
# => {"211"=>[3, 3], "213"=>[6, 1, 8], "214"=>[10, 11]}
We then can then map the result and sum the values:
stockmovements.map do |(k,v)|
{
item_id: k,
adjust_quantity: 0 - v.sum
}
end
# => [{:item_id=>"211", :adjust_quantity=>-6}, {:item_id=>"213", :adjust_quantity=>-15}, {:item_id=>"214", :adjust_quantity=>-21}]

Save multiple values into column

I'm trying to store multiple values retrieved from a JSONP request into my rails database so I can show the different results later.
I've created a function in my controller like this,
movieAdd.trailer(movie.id)
.then(function(response){
$scope.youtubeTrailer = response;
console.log ($scope.youtubeTrailer)
This requests the trailer service in my movieService.js,
var service = {};
var baseUrl = 'http://api.themoviedb.org/3/movie/';
function httpPromise (url) {
var deferred = $q.defer();
$http({
method:'JSONP',
url: url
})
.success(function(data){
deferred.resolve(data);
})
.error(function(){
deferred.reject();
});
return deferred.promise;
}
service.trailer = function(youtube_link){
return httpPromise(
baseUrl + youtube_link + '/videos?api_key=a8f7039633f2065942c**a28d7cadad4&callback=JSON_CALLBACK'
)
};
And I store the results in the youtubeTrailer scope.
Then I have a create function,
createMovie.create({
release_date: $scope.movieListID.release_date,
youtube_link: $scope.youtubeTrailer.key,
imdb_rating: $scope.movieImdbRating.imdbRating,
title: $scope.movieListID.original_title,
image: $scope.movieListID.poster_path,
movie_id: $scope.movieListID.id
}).then(init);
I've added the param to my movies_controller.rb
def movie_params
params.require(:movie).permit(:title, :image, :release_date, :youtube_trailer, :imdb_rating, :movie_id)
end
And I've created a column in my movies table
t.string "youtube_trailer"
But when I check my movies.json it says youtube_trailer":null
To recap, I get this kind of output,
{"id":1893,"results":[
{"id":"533ec65ac3a3685448000a24","iso_639_1":"en","key":"bD7bpG-zDJQ","name":"Trailer 1","site":"YouTube","size":720,"type":"Trailer"},
{"id":"533ec65ac3a3685448000a25","iso_639_1":"en","key":"UgDhFgSTPIw","name":"Trailer 2","site":"YouTube","size":720,"type":"Trailer"}
]}
And I'm trying to store both key values in my database so I can use them later.
You are expecting youtube_trailer but create this field as youtube_link. Please try to match the variable names.

Send 2 json from server to angularjs controller by 1 request

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.

Resources