In an integration test, I'm trying to save an invalid link. It is invalid because it links two nodes that belong to two different organizations (which my model validation does not permit). The error message displayed is however not the error message from my model validation but the error "Unable" from the controller.
I would have expected the validation from the model to come before this line in the controller. Moreover, I don't understand why, if we would take the model validation out of account, it wouldn't save. Could someone perhaps explain?
Part of my controller method:
if link.save
render json: #organization, message: "Saved", status: :created
else
render json: link, message: "Unable", status: :bad_request)
end
And in the Link model:
validate :same_org
def same_org
org1 = self.first_node.organization unless self.first_node.nil?
org2 = self.second_node.organization unless self.second_node.nil?
unless org1 == org2
errors.add(:second_node_id, "You can't link two nodes from different organizations")
end
end
From the api docs:
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save
"By default, save always run validations. If any of them fail the action is cancelled and save returns false."
So, you are correct in your assumption that validations (by default) run first. So the issue is that you are not passing that message to your view, not surprising as this line:
render json: link, message: "Unable", status: :bad_request)
Just passed back "Unable"
What you need to do is accesses the errors messages. replace "Unable" with
link.errors.full_messages.to_sentence
And you should be good.
this happens because at your controller you don't send validation error message so, you should change your controller code to something like
if link.save
render json: #organization, message: "Saved", status: :created
else
render json: {errors: link.errors.full_messages, message: "Unable"}, status: :bad_request
end
Related
Basically I have the following controller method:
def create
begin
#check_in = create_check_in()
rescue => exception
render json: { message: exception }, status: 500
end
end
and the following json.rabl file:
object #check_in => :event_check_in
attributes :id
What I try to achieve is to set manually the HTTP status code of the response. It currently responds with 200, and I need it to be 201 instead.
I saw very few similar question and the answer was generally to render / respond_with from the controller action, so I tried something like this:
def create
begin
#check_in = create_check_in()
render #check_in, status: 201
rescue => exception
render json: { message: exception }, status: 500
end
end
but all my attempts failed, throwing various errors.
Is there a way I could set the status code?
The issue is you're passing in #check_in as the first argument of the render method when it expects the first argument to be a hash of options, including the status option.
Your status: 201 option is being passed in as a hash to the methods second argument and being ignored.
Typicallya render call will look something like:
render json: #check_in.as_json, status: 201
# or more commonly something like
render action: :create, status: 201 # the #check_in variable is already accessible to the view and doesn't need to be passed in
# or just
render status: 201
# since by default it will render the view with the same name as the action - which is `create`.
There are lots of ways to call render, see the docs for more.
-- EDIT --
Max has a great comment - I'd strongly advise against rescuing from all exceptions and also against doing it in a specific controller action. In addition to his suggestion, Rails 5+ supports :api formatting for exceptions out of the box or, if you need more, I'd look at a guide like this one.
I have a React frontend backed by a Rails API and I am sending photos to Cloudinary through Active Storage. My settings are standard: I have an Event model with the method has_one_attached :photo, and the POST endpoint '/api/v1/events' is served by the 'Events#create' method with simply #event = Event.new(event_params). I also save an additional column 'url' in the 'events' table in order to display it back whenever needed (and I get it with #event.photo.url (a warning says that 'service_url' is said to be depreciated). I can fetch it (browser's fetch api) and use it in React. That works.
When I edit my form and submit the form with a new photo, then I have to adapt the PATCH query with the following events#update method. I followed the Rails guides that says you should purge and create_and_upload!.
def update
#event = Event.find(params[:id])
logger.debug "..................BEFORE : ..#{#event.to_json}"
if event_params[:photo] || #event.url
#event.photo.purge
#event.url = nil
end
if #event.update(event_params)
if event_params[:photo]
ActiveStorage::Blob.create_and_upload!(
io: File.open(event_params[:photo].tempfile),
filename: event_params[:photo].original_filename,
content_type:event_params[:photo].content_type
)
#event.url = #event.photo.url
end
logger.debug "................AFTER :.. #{#event.to_json}"
render json: {status: :ok}
else
render json: {errors: #event.errors.full_messages},
status: :unprocessable_entity, notice:"not authorized"
end
end
My logs show that this produces an url but no url is sent to React. If I modify other fields in this form, I get the update back in React from Rails. However, the link isn't sent: url: null.
I don't even use Active Job, but used 6.03 and even updated to 6.1. Anyone experienced this?
FI, the params hash contains the following:
"photo"=>#<ActionDispatch::Http::UploadedFile:0x00007fd938f1ecf8 #tempfile=#<Tempfile:/var/folders/11/whcyvzvx3w54zb0n1r0929200000gn/T/RackMultipart20200714-79173-t9s624.svg>, #original_filename="Rafting.svg", #content_type="image/svg+xml", #headers="Content-Disposition: form-data; name=\"event[photo]\"; filename=\"Rafting.svg\"\r\nContent-Type: image/svg+xml\r\n">}
In case of any interest, I found an answer on how to update Active Storage. My model is Event with a column url that contains a Cloudinary link, and the attached active storage object is named photo). Then on a PATCH, firstly purge if needed and then just update the event.url column with the method .url (formerly .service_url) doing:
event.update(url: event.photo.url)
def update
event = Event.find(params[:id])
# purge if a link already exists and the params contain a new picture
if event_params[:photo] && event.url
event.photo.purge
end
if event.update(event_params)
if event_params[:photo]
event.update(url: event.photo.url)
end
render json: event, status: :ok
else
render json: {errors: event.errors.full_messages},
status: :unprocessable_entity, notice:"not authorized"
end
end
The Rails guides seem misleading..
I have the following code
if user.update_attributes(user_params)
render json: user , status: "success"
else
render json: user.errors , status: "failed"
end
It updates the values if the object is correct . But let's say that email is duplicate then it does not goes in else condition . instead throws an exception.
ActiveRecord::RecordNotUnique (Mysql2::Error: Duplicate entry
'test#test.lo'
But as far i think . It should be a kind of thing which i could get like user.errors.full_messages .
I tried it using the following
if User.new(user_params).valid?
user.update_attributes(user_params)
render json: user , status: "success"
else
render json: user.errors , status: "failed"
end
It gets into else condition but user.errors.messages are equal to {}
You need to rely on model-level validation:
validates :email, uniqueness: true
If you rely on database-level constraints for validation, you're going to get exceptions upon write. You can capture those exceptions and attempt to turn them into a human-readable error message, but this isn't how Rails is meant to work.
If you want validation error messages, use validators in your models.
One of the tests in a scaffold-generated RSpec controller spec fails, and it looks to me as if it must always fail by design, but of course it is surely supposed to succeed.
I develop a Rails 4 app with RSpec specs generated by rails g scaffold.
The controller spec for my SkillsController requires me to fill in a 'valid attributes' hash and an 'invalid attributes' hash for my model, which I did.
The tests all succeed except for "PUT update with invalid params re-render the 'edit' template":
1) SkillsController PUT update with invalid params re-renders the 'edit' template
Failure/Error: expect(response).to render_template("edit")
expecting <"edit"> but rendering with <[]>
# ./spec/controllers/skills_controller_spec.rb:139:in `block (4 levels) in <top (required)>'
In the Rails console, I confirmed that my invalid_params hash contains invalid parameters ({ hack: 'hack' }).
The controller calls the skill_params method which returns an empty hash because my invalid_params hash contains only invalid parameters.
Calling skill.update(skill_params) with an empty skill_params hash returns true, so that the else part will never execute, and the 'new' template will not be rendered:
def update
respond_to do |format|
if #skill.update(skill_params) # empty hash due to invalid params!
format.html { redirect_to #skill, notice: 'Skill was successfully updated.' }
format.json { render :show, status: :ok, location: #skill }
else
format.html { render :edit }
format.json { render json: #skill.errors, status: :unprocessable_entity }
end
end
end
To summarize: The spec PUTs a hash with invalid parameters to my SkillController. The SkillController's 'skill_params' cleans up this hash, returning an empty hash. skill.update with an empty hash is a no-op (confirmed on the console), and the method returns true.
Therefore, the assertion that the 'edit' template should be rendered will never, ever be true, and the default controller spec for the update action with invalid params will never turn green.
What am I missing here?
I finally figured it out and post the solution here, in case someone else finds himself/herself in the same situation.
The 'invalid params' hash is not so much about parameters with invalid attribute names, it's more about invalid attribute values.
Invalid attribute names are simply ignored/discarded, just as #Simone Carletti pointed out in his response.
If invalid values are PUT to the controller, the mymodel.update method will return false, and the controller redirects to the :edit action.
Thus, in order to make the generated spec pass, the 'invalid attributes' hash at the top of the spec file should be filled with valid attribute names and invalid attribute values (e.g., nil, if an attribute is required).
This is what you should expect. When you use the strong parameters, the returned Hash only contains the attributes you explicitly permit.
This means that if you don't allow the hack parameter, then if you usmbit a request with
{ hack: 'hack' }
the results of skill_params will be
{}
By design, update is perfectly happy to accept an empty Hash. Therefore, if you execute
#skill.update({})
the result is true because the execution is successful. Of course, it didn't do anything, but it didn't fail.
Therefore, your controller will silently succeed and it will not run the edit condition.
I don't see why you would expect that passing a "not handled" param should cause the controller to show the edit page. It happens every day. Just take a random site and append random query strings to it. For example, go to
http://stackoverflow.com/?tab=interesting
and append a random query string
http://stackoverflow.com/?tab=interesting&foo=bar
The page will happily ignore it. It will not render an error, nor it will crash. The same happens to your controller, the unknown params such as hack as silently ignored and the controller renders the successful action.
I need to display error message on model in rails,
my coding on model is like this,
if my_address.valid?
# I need here the validation error.
return nil
end
I used errors.add("Invalid address") but it is not working
please help to solve this problem ,
You will be able to access the errors via object.errors, i.e. for your case my_address.errors. It will return Error objects, you can check up on it here: http://api.rubyonrails.org/classes/ActiveRecord/Errors.html
I suggest taking a look at how scaffolds (script/generate scaffold my_model) displays validation errors.
Here's a short summary:
def create
#post = Post.new(params[:post])
if #post.save # .save checks .valid?
# Do stuff on successful save
else
render :action => "new"
end
end
In the "new" view, you'll use #post.errors, most likely with <%= error_messages_for :post %>.