Rails update action fails to check params - ruby-on-rails

I am building a Rails API and found out that put request passes without required parameters. That is weird for me as app won't allow post request without parameters. Moreover, when I’m trying to update the spending without attributes via Rails console, it fails. But via Postman/CURL request passes successfully
The controller looks like this:
class SpendingsController < ApplicationController
before_action :find_spending, only: %i[show update destroy]
def create
spending = Spending.new(spending_params)
spending.user = current_user
spending.category = Category.find_by(id: spending_params[:category_id])
if spending.valid?
spending.save
render json: SpendingSerializer.new(spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(spending), status: :bad_request
end
end
def index
spendings = Spending.where(user_id: current_user.id).order("#{sort_spendings}")
total_value = Spending.where(user_id: current_user.id).pluck(:amount).sum
render json: {spendings: SpendingSerializer.new(spendings), total_amount: total_value}, status: :ok
end
def show
if #spending.valid?
render json: SpendingSerializer.new(#spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :not_found
end
end
def update
if #spending.valid?
#spending.update(spending_params)
render json: SpendingSerializer.new(#spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :bad_request
end
end
def destroy
if #spending.destroy
head :no_content
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :not_found
end
end
private
def spending_params
params.require(:spending).permit(:description, :amount, :category_id)
end
def find_spending
begin
#spending = Spending.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: {errors: "Spending with id #{params[:id]} not found"}, status: :not_found
end
end
def sort_spendings
sort = { sort_by: "created_at", sort_dir: "desc"}
sort[:sort_by] = params[:sort_by].split(" ").first if params[:sort_by].present?
sort[:sort_dir] = params[:sort_by].split(" ").last if params[:sort_by].present?
sort.values.join(" ")
end
end
And my model:
class Spending < ApplicationRecord
belongs_to :user
belongs_to :category
validates :description,
presence: true
end
I’m really out of ideas, why is that happening. Any guesses what can that be related to?

First thing that I noticed is your update method. You check validation before updating the model. #spending.valid? always returns true in this case. My suggestion to modify it. #spending.update(spending_params) returns true if it update is successful and false if it fails.
def update
if #spending.update(spending_params)
render json: SpendingSerializer.new(#spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :bad_request
end
end
created method an be also optimised. You don't need find and assign category separately. It will be assigned as all spending_params.
def create
spending = Spending.new(spending_params)
spending.user = current_user
spending.save
render json: SpendingSerializer.new(spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(spending), status: :bad_request
end
end

Related

How to use delayed job for a method in ActiveModel (Ruby on Rails)

I have a controller as
class Api::V2::Events::WegSessionsController < Api::V2::Events::ApplicationController
def synchronize
if #service.valid_connection?
#service.delay.synchronize
render json: {
status: :ok,
message: #service.message
}, status: :ok
else
render json: {
status: :unprocessable_entity,
errors: #event.errors.full_messages
}, status: :ok
end
..Service instantiation and other code
end
end
and the service class looks like
class WegService
include ActiveModel::Model
def synchronize
if event.weg_synced_at.present?
update
else
create
end
event.update(weg_synced_at: Time.current)
end
...Some code
end
But I am not able to execute this since my method #service.delay.synchronize is throwing an error.
<ArgumentError: job cannot be created for non-persisted record:
What should I do here?

How to validate column can be updated once?

I have an agreement model and it has a released_at column. I want to validate that agreement is can be released once. How can I do that?
Controller:
# GET /aggreements/1/release
def release
#agreement.update(released_at: Time.now.utc)
if #agreement.save
render json: {success: ["Agreement released."]}
else
render json: #agreement.errors, status: :unprocessable_entity
end
end
Model:
class Agreement < ApplicationRecord
validate :released_agreement_cannot_be_released
def released_agreement_cannot_be_released
if released_at.present?
errors.add(:released_at, "already released")
end
end
end
Thank you.
I've figured it out, here is the solution.
Controller:
# GET /agreements/1/release
def release
#agreement.released_at = Time.now.utc
if #agreement.save
render json: {success: ["Agreement released."]}
else
render json: #agreement.errors, status: :unprocessable_entity
end
end
Model:
class Agreement < ApplicationRecord
validate :released_agreement_cannot_be_released
def released_agreement_cannot_be_released
if released_at_was.present?
errors.add(:released_at, "already released")
end
end
end

add XML support to JSON handling functions in ROR

In my heroku RoR app,I have a controller in which I support JSON requests. There I have the following functions:
def mssg_as_json
#message = Message.new
#message.text = params.require(:messages)
#message.save
string = "http://link.com/"
#message.url = string + #message.id.to_s
#message.save
render json: { url: #message[:url] }
end
def return_mssg_as_json
if #message = Message.find_by(id: params[:id])
render json: { message: #message[:text] }
else
render json: {errors: :not_found}, status: :not_found
end
end
I want to support XML requests too. My idea is to somehow convert the XML to JSON but I have no idea how. How can I modify the code to support both XML and JSON?
P.S.
My routes are:
get "messages/api" => "messages#return_mssg_as_json"
post "messages/api" => "messages#mssg_as_json"
The requests are send to main_url/messages/api
So, you should take advantage of the standard routes and actions. In routes.rb, you might do something like:
Rails.application.routes.draw do
resources :messages
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :messages
end
end
end
Note that your controller is now nested inside api/v1. That allows you to identify the path as an api and to maintain versions over time. That solid's practice. Also note that you have a standard messages resource for your web app.
Then, your controller would look like:
class Api::V1::MessagesController < Api::V1::BaseController
def create
#message = Message.new(message_params)
respond_to do |format|
if #message.save
#message.update(url: "http://link.com/#{#message.id}")
format.json { render json: create_hsh, status: :ok }
format.xml { render xml: create_hsh, staus: :ok }
else
format.json { render json: #message.errors, status: :unprocessable_entity }
format.xml { render xml: #message.errors, status: :unprocessable_entity }
end
end
end
def show
respond_to do |format|
if #message = Message.find_by(id: params[:id])
format.json { render json: show_hsh, status: :ok }
format.xml { render xml: show_hsh, status: :ok }
else
format.json { render json: {errors: :not_found}, status: :not_found }
format.xml { render xml: {errors: :not_found}, status: :not_found }
end
end
end
private
def create_hsh
#message.attributes.with_indifferent_access.slice(:url)
end
def show_hsh
attr = #message.attributes.with_indifferent_access
attr.slice(:foo, :bar).merge!(message: attr[:text])
end
def message_params
params.require(:message).permit(:text)
end
end
Note that this controller inherits from Api::V1::BaseController. That would be a controller that you set up to do api-relevant client authentication (key/token checking, etc.). Something, perhaps, like:
class Api::V1::BaseController < ActionController::API
before_action :authorize
def authorize
# do your checks in a way that results in a variable #authorized?
render(json: {errors: :unauthorized}, status: :unauthorized) unless #authorized?
end
end
So, now you're using a single controller action to respond to all format types (that you elect to offer). Then, you clients would post something like:
http://your.api.com/messages.json
To get a json response. Or:
http://your.api.com/messages.xml
To get an xml response. You might notice the bit that says: namespace :api, defaults: {format: 'json'} do. This means that your client could call:
http://your.api.com/messages
and it will default to the json format.
Now you don't have a bunch of random endpoints (like mssg_as_json), just the regular RESTful ones that your clients will expect. Your API clients will love you for that.
You'll note that show_hsh is the accepted code from your earlier question.

How can a user log in a user right after signing up?

I know I should put the code in the create action of the users controller, but I'm not sure what code.
this is my controller code:
# frozen_string_literal: true
class UsersController < ProtectedController
skip_before_action :authenticate, only: [:signup, :signin]
# POST '/sign-up'
def signup
user = User.create(user_creds)
if user.valid?
render json: user, status: :created
else
render json: user.errors, status: :bad_request
end
end
# POST '/sign-in'
def signin
creds = user_creds
if (user = User.authenticate creds[:email],
creds[:password])
render json: user, serializer: UserLoginSerializer, root: 'user'
else
head :unauthorized
end
end
# DELETE '/sign-out/1'
def signout
if current_user == User.find(params[:id])
current_user.logout
head :no_content
else
head :unauthorized
end
end
# PATCH '/change-password/:id'
def changepw
if !current_user.authenticate(pw_creds[:old]) ||
(current_user.password = pw_creds[:new]).blank? ||
!current_user.save
head :bad_request
else
head :no_content
end
end
def index
render json: User.all
end
def show
user = User.find(params[:id])
render json: user
end
def update
head :bad_request
end
private
def user_creds
params.require(:credentials)
.permit(:email, :password, :password_confirmation)
end
def pw_creds
params.require(:passwords)
.permit(:old, :new)
end
private :user_creds, :pw_creds
end
i know i should change something
in my create user but not sure where
i tried to use #current_user = user under the sign up part but it didnt work.
Do you want user to be signed in when he/she sign up?
# POST '/sign-up'
def signup
user = User.create(user_creds)
if user.valid?
User.authenticate(user_creds[:email], user_creds[:password])
render json: user, status: :created
else
render json: user.errors, status: :bad_request
end
end

In Rails, why am I getting a "204 - No Content" response for my update/PATCH/PUT, using Active Model Serializers?

This code is for a UserList (a user can create a User To-Do List). This particular resource does not hold the list items, but just the title of the list, and the type of list.
class Api::V1::UserListsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
def index
if authenticate_user
user_lists = #current_user.user_lists
if user_lists
respond_with user_lists, each_serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's lists."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def show
if authenticate_user
user_lists = #current_user.user_lists
user_list = user_lists.find_by_id(params[:id])
if user_list
respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's list."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def create
if authenticate_user
user_list = #current_user.user_lists.new(user_list_params)
if (user_list.save!)
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not create new User List."}, status: :unprocessable_entity
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def update
if authenticate_user
user_list = #current_user.user_lists.find_by_id(params[:id])
if (user_list.update_attributes(user_list_update_params))
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
#respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not update User List." }, status: :unprocessable_entity
end
end
end
private
def user_list_params
params.require(:user_list).permit(:user_id, :type_id, :title)
end
def user_list_update_params
params.require(:user_list).permit(:type_id, :title)
end
end
Now the update works when I PUT/PATCH... but I get a
Completed 204 No Content in 24ms (ActiveRecord: 4.3ms)
It's been about 4+ months since I've done any rails, and back then I was only just beginning to learn it.
1) Does anyone know why I'm not getting anything back? I know it's something to do with my respond_with line of code in update, but I'm not sure exactly what.
2) Can someone clarify to me the difference between the SHOW respond_with and the CREATE respond_with. I recall having an issue grasping this back then, and obviously now.
SHOW
respond_with user_list, serializer: Api::V1::UserListSerializer
CREATE
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
a) Why does create require :api and :v1 first, but show does not?
b) Why does create require the #current_user, but show does not?
Appendix: Here is my Serializer for reference
class Api::V1::UserListSerializer < ActiveModel::Serializer
attributes :id, :user_id, :type_id, :title
has_many :items, embed: :ids
end
I know this is 2 years too late, but after some digging, I found the empty response with the 204 is intentional (as mentioned above). If you use respond_with this will always be the case. A workaround would be to use render instead (example below):
class Api::V1::ItemsController < ApplicationController
respond_to :json
...
def update
#item = Item.find(params[:id]
if #item
#item.update_attribute(item_params)
render json: #item
end
end
...
end
You're not supposed to get anything back other than the 204. Any intelligent client does not need to receive back the data it just sent you -- it needs only confirmation that the data was persisted.
Do not mistakenly pass your class Api::V1::UserListSerializer as a key/value pair (Hash form). You will get an error including the text class or module needed. It should look like this:
serialize :some_array, Api::V1::UserListSerializer
Or, perhaps clearer would be:
serialize(:some_array, Api::V1::UserListSerializer)
You miss one param and you are rendering an object class with no content : 204 - No Content
That may seem obvious, but it is common to be in the habit of passing things as a key/value pair.
One improve:
before_action :authenticate_user, only: [:create, :show, :update, ...]
https://apidock.com/rails/ActiveRecord/Base/serialize/class
def update
#item = Item.find(params[:id])
respond_with(:api, :v1, #item) do |format|
if #item.update(item_params)
format.json { render json: #item}
else
format.json { render json: {error: #item.errors.full_messages}}
end
end
end

Resources