Rails 5 Api model not saving fields - strong-parameters

I have a rails 5.2 API that currently creates and authenticates users (tested in Postman). However, I have added another model called Stories and when I attempt to create a new Story, a story is created but only the id attribute is saved.
Here is my StoriesController:
class StoriesController < ApplicationController
before_action :set_story, only: [:show, :update, :destroy]
# GET /stories
def index
#stories = Story.all
render json: #stories
end
# POST /stories
def create
#story = Story.new(story_params)
if #story.save
render json: #story, status: :created
else
render json: { errors: #story.errors.full_messages },
status: :unprocessable_entity
end
end
# GET /stories/:id
def show
render json: #story
end
# PUT /stories/:id
def update
#story.update(story_params)
head :no_content
end
# DELETE /stories/:id
def destroy
#story.destroy
head :no_content
end
private
def story_params
# whitelist params
params.permit(:title, :category, :summary)
end
def set_story
#story = Story.find(params[:id])
end
end
Here is the request in Postman:
Here is the output in Postman:
I thought this was a params issue, but that does not seem to be it.

man there is an extra opening bracket on your post payload on line 3.
The right json payload is.:
{
"story": {
"title": "Spiderman"
"category": "Superhero"
"summary": "new spiderguy ftw"
}
}
I think there is a issue with your story_params method. The Story attributes "tittle", "category" and "summary" are nested in "story".
You can try this:
params.require(:story).permit(:title, :category, :summary)

Related

How to render some specific information of the loggedinUser?

Hi I have react front end and rails backend.There is login and logout system implemented. The app is a reservation booking app.So when I make a post request to my reservations I want only reservations of logged In user to appear under My reservation tab.Currently Reservations of all the users are appearing under that sections.(As in different people logged in from different accounts everyones reservations can be seen).How can I filter only reservations of logged in user Pls check out my code .
Application controller
class ApplicationController < ActionController::API
include ActionController::Cookies
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
before_action :authorize
private
def authorize
puts "hello",session[:user_id]
#current_user = User.find_by(id: session[:user_id])
render json: { errors: ["Not authorized"] }, status: :unauthorized unless #current_user
end
def render_unprocessable_entity_response(exception)
render json: { errors: exception.record.errors.full_messages }, status: :unprocessable_entity
end
end
Here are my routes for reference
Rails.application.routes.draw do
resources :reservations,only: [:index,:create,:update,:destroy]
resources :reviews,only: [:index,:create,:destroy]
resources :restaurants,only: [:index]
post "/signup", to: "users#create"
get "/me", to: "users#show"
post "/login", to: "sessions#create"
delete "/logout", to: "sessions#destroy"
end
User controller
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
skip_before_action :authorize, only: [:create]
def create
user = User.create!(user_params)
session[:user_id] = user.id
render json: user, status: :created
end
def show
render json: #current_user
end
def index
user=User.all
render json: user
end
private
def user_params
params.permit(:name,:email,:password)
end
def render_unprocessable_entity_response(invalid)
render json: { errors: invalid.record.errors.full_messages }, status: :unprocessable_entity
end
end
Sessions controller
class SessionsController < ApplicationController
skip_before_action :authorize, only: :create
def create
user = User.find_by(email: params[:email]) #verifying
if user&.authenticate(params[:password])
session[:user_id] = user.id
render json: user
else
render json: { errors: ["Invalid username or password"] }, status: :unauthorized
end
end
def destroy
session.delete :user_id
head :no_content
end
end
Reservation controller
class ReservationsController < ApplicationController
def index
reservation =Reservation.all
render json: reservation
end
def create
reservation=Reservation.create!(reservation_params)
render json: reservation,status: :created
end
def update
reservation = Reservation.find_by(id: params[:id])
review.update!(reservation_params)
render json: reservation,status: :ok
end
def destroy
reservation = #current_user.reservation.find(params[:id])
if #current_user
reservation.destroy
else
render json: {error: "Reservation of someone else."}, status: :not_found
end
end
private
def reservation_params
params.permit(:name, :date, :time, :num, :contact, :occasion,:user_id,:restaurant_id)
end
end
For my front end
import { useState,useEffect } from "react";
import ReservationCard from "./ReservationCard";
function MyReservations({user}){
const[reservations,setReservations]=useState([]);
useEffect(()=>{
fetch("/reservations")
.then(res=>res.json())
.then(reservationData=>{
setReservations(reservationData)
})
},[])
return(
<>
<h1>My Reservations</h1>
{reservations.map((reservation)=>(
<ReservationCard key={reservation.id} reservation={reservation} />
))
}
</>
)
}
export default MyReservations;
I guess that your User model has a line like this
has_many :reservations
and because the current user is stored in #current_user in the authorize method you should be to only show the current user's reservations by changing the ReservationsController#index method to:
def index
reservations = #current_user.reservations
render json: reservations
end

Rails/React App Signup throwing 500 internal server error

I'm trying to get user data to save to my Rails DB for user info through a sign up form on a React front end. I've got my route and controller written properly (at least I think) and the fetch request below but I keep getting a 500 internal error through on submit. Please let me know what I'm missing, any help would be greatly appreciated!
My route:
resources :users, only: [:show, :create]
My create action in UsersController:
def create
user = User.create!(user_params)
if user.valid?
session[:user_id] = user.id # remembering who our user is
render json: user, status: :ok
else
render json: {error: user.errors.messages}
end
end
and lastly my fetch request from the Signup.js component on the React frontend, where I'm getting the error on the line that has 'fetch'
fetch(`/users`,{
method:'POST',
headers:{'Content-Type': 'application/json'},
body:JSON.stringify(user)
})
.then(res => {
if(res.ok){
res.json().then(user => {
history.push(`/users/${user.id}`)
})
This might only be part of your problem, but first creating, then asking for validity is backwards.
Do something like this instead:
def create
user = User.new(user_params)
if user.save # <-- will return false if the save fails
user.reload
session[:user_id] = user.id # remembering who our user is
render json: user, status: :ok
else
render json: {error: user.errors.messages}
end
end
If you really want to check validity explicitly:
def create
user = User.new(user_params)
if user.valid? # <-- will check validity
user.save
user.reload
session[:user_id] = user.id # remembering who our user is
render json: user, status: :ok
else
render json: {error: user.errors.messages}
end
end
My guess is your error might be coming from the fact that your user variable doesn't actually have an ID yet. You need to save the record, then refresh it to get an ID.

How to use Active Storage to add profile picture for User

Recently, I tried using active storage in Ruby on Rails to store profile picture for logged in user. I been following this documentation closely but, it didn't work out https://edgeguides.rubyonrails.org/active_storage_overview.html.
What I want is to allow users to register with only first name, last name, username, password, and email. Then, users can login and they can upload their own profile picture from profile page. After uploading, I want users to see their avatar in their profile.
My User.rb Model is looks like this:
class User < ApplicationRecord
has_secure_password
has_many :games, dependent: :destroy
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
validates :password, presence: true
has_one_attached :avatar
end
I added "has_one_attached :avatar" like from the rails documentation and I'm using "set_avatar" method to attach avatar to an existing user.
My users_controller is looks like this:
class UsersController < ApplicationController
skip_before_action :authorized
wrap_parameters format: []
def index
users = User.all
render json: users
end
def find_user
user = User.find_by(id: params[:id])
if user
render json: user, status: :not_found
else
render json: { error: "Not found" }, status: :not_found
end
end
def show
user = User.find_by(id: session[:user_id])
if user
render json: user
else
render json: { error: "Not authorized" }, status: :unauthorized
end
end
def create
user = User.create(user_params)
if user.valid?
session[:user_id] ||= user.id
render json: user, status: :created
else
render json: {error: user.errors.full_messages }, status: :unprocessable_entity
end
end
def update
currentUser = User.find_by(id: params[:id])
if currentUser
currentUser.update(user_update_params)
render json: currentUser, status: :accepted
else
render json: {error: currentUser.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
user = User.find_by(id: params[:id])
if user
user.destroy
head :no_content
else
render json: {error: "user not found"}, status: :not_found
end
end
def set_avatar
user = User.find_by(id: params[:id])
if user
user.avatar.attach(params[:avatar])
else
render json: {error: "Profile image upload failed"}
end
end
private
def user_params
params.permit(:first_name, :last_name, :email, :username, :password, :avatar)
end
def user_update_params
params.permit(:first_name, :last_name, :email, :username, :password)
end
end
My routes:
Rails.application.routes.draw do
# resources :game_memos
# resources :memos
# resources :games
# resources :users
resources :sessions
#--------------USER------------------------------
# Create Session
get '/login', to: "sessions#create"
# Show all users
get '/users', to: "users#index"
# Register new user
post '/users', to: "users#create"
# Login User
post "/login", to: "sessions#create"
# Logout user
delete '/logout', to: "sessions#destroy"
# Update User profile
patch '/users/:id', to: "users#update"
# Keep user logged in
get '/me', to: "users#show"
# Get request for find user based on User id
get '/users/:id', to: "users#find_user"
# Save avatar
post '/users/:id', to: "users#set_avatar"
# -------------GAMES Routes----------------------
get '/games', to: 'games#index'
# Show all the games that belongs to logged in user
get '/users/:id/games', to: "games#show"
# Create a new wishlist
post '/games', to: "games#create"
# Delete a game from wishlist
delete '/users/:id/games/:id', to: "games#destroy"
#-------------Memo Routes------------------------
# Show memos that belongs to a game
get '/games/:id/memos', to: "memos#show"
# Post memos to a game
post '/games/:id/memos', to: "memos#create"
end
I'm uploading my avatar from this component:
import React, { useState } from "react";
// I'm getting default image from this location just for now
import IMAGES from '../images/Image';
export default function ProfilePicture({currentUser}){
const[profileAvatar, setAvatar] = useState([])
const fileTypes = [
"image/apng",
"image/bmp",
"image/gif",
"image/jpeg",
"image/pjpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/webp",
"image/x-icon"
];
function handleSubmit(e){
// fetch(`/users/${currentUser.id}` ,{
// method: "POST",
// headers: {"Content-Type": "application/json"},
// body: JSON.stringify({avatar:profileAvatar})
// })
// .then((r) => r.json())
// .then(data => {
// if (data.errors) {
// alert(data.errors)
// }
// else {
// setAvatar(data)
// }
// })
}
return(
<div>
<img src={IMAGES.defaultProfile} alt="default_profile_image" className="profile_avatar"/>
<form onSubmit={handleSubmit}>
<input type="file" id="avatar" name="avatar" accept={fileTypes} onChange={(e)=>setAvatar(e.target.value)}/>
<button type="submit">Submit</button>
</form>
</div>
)
}
Lastly, my Users migration file:
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.string :username
t.string :password_digest
t.string :avatar
t.timestamps
end
end
end
I wasn't sure where to ask this kind of question. I never used Active Storage before and this is my first time using it. Please help.
Firstly you don't need t.string :avatar. ActiveStorage attachments are not columns of owner table but records in associated tables
ActiveStorage provides Direct Upload if you use JS frameworks like React
From docs:
If you want to use the Direct Upload feature from a JavaScript framework, or you want to integrate custom drag and drop solutions, you can use the DirectUpload class for this purpose. Upon receiving a file from your library of choice, instantiate a DirectUpload and call its create method. Create takes a callback to invoke when the upload completes.
import { DirectUpload } from "#rails/activestorage"
const input = document.querySelector('input[type=file]')
// Bind to file drop - use the ondrop on a parent element or use a
// library like Dropzone
const onDrop = (event) => {
event.preventDefault()
const files = event.dataTransfer.files;
Array.from(files).forEach(file => uploadFile(file))
}
// Bind to normal file selection
input.addEventListener('change', (event) => {
Array.from(input.files).forEach(file => uploadFile(file))
// you might clear the selected files from the input
input.value = null
})
const uploadFile = (file) => {
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const url = input.dataset.directUploadUrl
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add an appropriately-named hidden input to the form with a
// value of blob.signed_id so that the blob ids will be
// transmitted in the normal upload flow
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
}
})
}
Pay attention to const url = input.dataset.directUploadUrl. You need to set data-direct-upload-url attribute as your upload url, by default it is /rails/active_storage/direct_uploads
hiddenField.setAttribute("value", blob.signed_id) from this example is a Rails magic
When the file is uploaded, Rails return blob with signed_id to frontend. Using it, you can attach file to the record
In your case
fetch(`/users/${currentUser.id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({avatar: blob.signed_id}) // blob.signed_id we get after file uploading
})
On backend
def set_avatar
user = User.find(params[:id])
user.update(avatar: params[:avatar])
render json: { avatar: rails_blob_path(user.avatar) }
end
So pass blob.signed_id and avatar will be attached automatically
Probably this tutorial will be helpful for you
Like Mechnicov's answer you don't need t.string :avatar
in the schema the
has_one_attached :avatar is already taking care of that.
If you want to attach the photo through the backend and not direct upload try using this
user.avatar.attach(io: file, filename: 'photo.png', content_type: 'image/png')
and make sure that the client is sending the photo through formdata and not through the body
Step 1: Create a FormData instance
let formData = new FormData();
Step 2: Append the data in it
formData.append('file_to_upload', fileRef.files[0]); // fileRef is the input file reference

HTTP 400 error: formatting properly a JSON request using React

I'm working on a POST request from React to Ruby on Rails backend; I tested the endpoint using Postman with the bellow data in JSON format:
{
"amount": 2,
"name": "Cheese and Loroco",
"price": "1.55"
}
And it worked(status 200); now, from React my JSON request looks like this:
{"ordertemp":[{"name":"Cheese and Loroco","amount":1,"price":"1.25"}]}
And I'm obtaining a status 400.
So the code that I'm using to generate the request is the bellow:
await fetch("http://localhost:3000/ordertemps", {
method: 'POST',
BODY: JSON.stringify({
//user: userData,
ordertemp: cartCtx.items,
}),
});
About my backend, I'm obtaining this otuput:
My controller sourcecode looks like this:
class OrdertempsController < ApplicationController
#GET /ordertemps
def index
#ordertemps = Ordertemp.all
render json: #ordertemps
end
#GET /ordertemp/:id
def show
#ordertemp = Ordertemp.find(params[:id])
render json: #ordertemp
end
#POST /ordertemps
def create
#ordertemp = Ordertemp.new(ordertemp_params)
if #ordertemp.save
render json: #ordertemp
else
render error: { error: 'Unable to create an order'}, status: 400
end
end
#PUT /ordertemps/:id
def update
#ordertemp = Ordertemp.find(params[:id])
if #ordertemp
#ordertemp.update(ordertemp_params)
render json: { message: 'Order successfully updated.'}, status: 200
else
render json: { error: 'Unable to update the order.', status: 400}
end
end
#DELETE /ordertemps/:id
def destroy
#ordertemp = Ordertemp.find(params[:id])
if #ordertemp
#ordertemp.destroy
render json: { message: 'Order successfully deleted.'}, status: 200
else
render json: { error: 'Unable to delete Order.'}, status: 400
end
end
private
def ordertemp_params
#params.require(:ordertemp).permit( :clientName, :clientId, :amount, :mealid, :name, :price)
params.require(:ordertemp).permit(:amount, :name, :price)
end
end
So, I would like to request your help with the next questions:
should I get rid off the "ordertemp" at the beginning of my JSON from React? in the case yes, how can I accomplish it?
what else am I forgetting to get a status 200 instead of 400?
Thanks a lot
I don't do ruby, but with postman you are sending this
{
"amount": 2,
"name": "Cheese and Loroco",
"price": "1.55"
}
whereas on react you are sending this:
{"ordertemp":[{"name":"Cheese and Loroco","amount":1,"price":"1.25"}]}
They are not the same, so start with the same use case and see if it works.
Also, BODY should be body and you could add headers like in the fetch example
I noticed in your controler that Ordertemp is the a capitalized letter (I don't know if it matters)

How to send the correct request to my rails-api thanks to ng-resource?

I created an API using the rails-api gem and I have an client app based on angular in which I use ng-resource.
I do think that the request I send to my API should be more like {post=>{"kind"=>"GGG"}} and not {"kind"=>"GGG"} of I have to find a way for my api to work with the request I send now. For now I'm stuck with 400 Bad Request errors and I can't find out how to fix it.
Here is my rails controller :
class PostsController < ApplicationController
# GET /posts
# GET /posts.json
skip_before_filter :verify_authenticity_token, :only => [:update, :create]
def index
#posts = Post.all
render json: #posts
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
render json: #post
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(post_params)
if #post.save
render json: #post, status: :created, location: #post
else
render json: #post.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
#post = Post.find(params[:id])
if #post.update(params[:post])
head :no_content
else
render json: #post.errors, status: :unprocessable_entity
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
head :no_content
end
private
def post_params
params.require(:post).permit(:post, :kind)
end
end
Here is my angular controller :
$scope.postData = {};
$scope.newPost = function() {
console.log($scope.postData);
var post = new Post($scope.postData);
post.$save($scope.postData);
}
Here is my angular factory :
.factory('Post', function($resource) {
return $resource('http://localhost:3000/posts');
})
In my logs I have :
Started POST "/posts?kind=GGG" for 127.0.0.1 at 2014-05-26 18:21:21 +0200
Processing by PostsController#create as HTML
Parameters: {"kind"=>"GGG"}
Completed 400 Bad Request in 2ms
ActionController::ParameterMissing (param is missing or the value is empty: post):
app/controllers/posts_controller.rb:55:in `post_params'
app/controllers/posts_controller.rb:23:in `create'
-
Change the following code:
def post_params
params.require(:post).permit(:post, :kind)
end
To be:
def post_params
params.permit(:post, :kind)
end
And you problem will be fixed.

Resources