Rails undefined method `persisted?' - ruby-on-rails

I working using rails
my project make API using mongodb
and I got this error:
NoMethodError: undefined method `persisted?'for ActionController::Parameters:0x000055f487fc4ac8
This error is in my controller on create method:
def create
if #merchant.order.push(get_params)
render json: {:response => true, :status => "ok"}
else
render json: {:response => false, :status => "ok"}
end
end
def get_params
params.required(:order).permit!
end
This is my model:
class Order
include Mongoid::Document
include Mongoid::Timestamps
field :items
field :grand_total, type: Integer
belongs_to :merchant
end
I appreciate all kind of support, thank you.

push accept an instance of Order and I assume you are passing something like ActionController::Parameters. Also, push always returns an association. I think if this failed, it would be with an exception and then the if makes no sense. I suggest to use create instead. So (assuming get_params is an instance of ActionController::Parameters or a Hash and that order is a has_many relationship):
if #merchant.order.create(get_params)
render json: {:response => true, :status => "ok"}
else
render json: {:response => false, :status => "ok"}
end
end
If it is a hash_one relationship, it should be something like:
params = get_params.merge(merchant: #merchant)
if #Order.create(params)
render json: {:response => true, :status => "ok"}
else
render json: {:response => false, :status => "ok"}
end
end

As far as I understand, push accepts a record (a model), not a hash of params:
label = Label.first
label.bands.push(Band.first)
Docs
Mongoid checks if the model is persisted, that's why #persisted? is called on ActionController::Parameters that you pass there.
Try sth like
#merchant.order.push(Order.new(get_params))
if order is has_many relationship or
#merchant.order = Order.new(get_params)
if order is has_one relationship.

Related

render status when validation fails

I am working on a legacy Rails 2 project,
In my model class, I have a validation:
class Student < ActiveRecord::Base
validates_uniqueness_of :foo, :scope => [:type, :gender], :message => "Already have such student"
...
end
It checks for uniqueness of field foo based on type and gender attributes, if a student with these attributes already exist while creating a new student, an error message is raise.
My question is, with this validation, instead of having that error message, how can I call render :status => 422, :json=>"Already have such student" ? Is it possible
==== controller ====
class StudentsController < BaseController
def create
student = Student.new({...})
# Since there are other validations in Student class, it could be any reason student is nil here.
if student.nil?
render :status => :unprocessable_entity, :json => "Failed to create student."
else
render :status => :ok, :json=> student.to_json
end
end
end
Try with this code
class StudentsController < BaseController
def create
student = Student.new({...})
if student.save
render :status => :ok, :json=> student.to_json
else
render :status => :unprocessable_entity, :json => student.errors.full_messages
end
end
end
Actually student is never nil even if it is not valid. So your code will always render ok

Pass one variable to next in JSON object in Rails 5

I need to be able to access user data in comments.
def index
#user = User.all
#libraries = Library.all.order('created_at ASC')
#comments = Comment.all
#user_likes = UserLike.all
render :json => #libraries, :include => [:user, :comments, :user_likes]
end
I tried this:
render :json => #libraries, :include => [:user, {:comments => :user}, :user_likes]
And it did not work. Any ideas?
Assuming you have the associations set up, you need to add include for the nested associations:
render :json => #libraries,
:include => {:user, {:comments => { include: :user}}, :user_likes}
When rendering JSON like this, Rails calls as_json first to convert the payload to hash. That's when the option include is used. You can check the documentation to see how it works.

Return proper errors from API in Rails

I have the following #create method:
def create
begin
#order = #api_user.orders.create!(order_params)
render :json => #order, :only => [:id], :status => :created, :location => #order
rescue
render :json => {}, :status => :unprocessable_entity
end
end
However, I am using a generalistic approach for the rescue. If the order could not be created because one of the passed fields failed the validation, I would like to let the user know about that. So, if the creation of the order raised this:
ActiveRecord::RecordInvalid: Validation failed: Description1 is too long (maximum is 35 characters)
What is the proper way of catching and letting the API user know about it?
One thing you can do is make use of a light API library like rocketpants (https://github.com/Sutto/rocket_pants)
in which case, the method you want could be written like this:
def create
if #order = #api_user.orders.create!(order_params)
expose #order
else
error! :bad_request, :metadata => {:error_description => "#{#order.errors.full_messages}"}
end
end
This is assuming you have set the #api_user instance variable earlier somewhere. Also, the gem uses Active Model Serializers (https://github.com/rails-api/active_model_serializers) to serialize the #order into JSON, so you can always customize the output to your liking by creating a basic serializer, look at the github page for more info :)
Here is another way:
def create
#order = #api_user.orders.build(order_params)
if #order.save
render :json => #order,
:only => [:id], :status => :created, :location => #order
else
render :status => :unprocessable_entity,
:json => {:errors => #order.errors.full_messages}
end
end
You'll get back an array of errors in the JSON

Rails as a backend API

I am completely new to rails (actually this is my day 1 of rails). I am trying to develop a backend for my iOS app. Here is my create user method.
def create
user = User.find_by_email(params[:user][:email])
if user
render :json => {:success => 'false', :message => 'Email already exists'}
else
user = User.new(user_params)
if user.save
render :json => {:success => 'true', :message => 'Account has been created'}
else
render :json => {:success => 'false', :message => 'Error creating account'}
end
end
end
How can I make it better?
You could use HTTP status code, but it might be overkill if your API is not going to be used by anything but your iOS app.
The way I would do it is to put the validation on the model's side and let ActiveModel populate the errors. Status codes are also super useful.
class User < ApplicationModel
validate_uniqueness_of :email
# Other useful code
end
class UsersController < ApplicationController
def create
#user = User.new(params.require(:user).permit(:email)) # `require` and `permit` is highly recommended to treat params
if #user.save # `User#save` will use the validation set in the model. It will return a boolean and if there are errors, the `errors` attributes will be populated
render json: #user, status: :ok # It's good practice to return the created object
else
render json: #user.errors, status: :unprocessable_entity # you'll have your validation errors as an array
end
end
end

How to build a JSON response made up of multiple models in Rails

First, the desired result
I have User and Item models. I'd like to build a JSON response that looks like this:
{
"user":
{"username":"Bob!","foo":"whatever","bar":"hello!"},
"items": [
{"id":1, "name":"one", "zim":"planet", "gir":"earth"},
{"id":2, "name":"two", "zim":"planet", "gir":"mars"}
]
}
However, my User and Item model have more attributes than just those. I found a way to get this to work, but beware, it's not pretty... Please help...
Update
The next section contains the original question. The last section shows the new solution.
My hacks
home_controller.rb
class HomeController < ApplicationController
def observe
respond_to do |format|
format.js { render :json => Observation.new(current_user, #items).to_json }
end
end
end
observation.rb
# NOTE: this is not a subclass of ActiveRecord::Base
# this class just serves as a container to aggregate all "observable" objects
class Observation
attr_accessor :user, :items
def initialize(user, items)
self.user = user
self.items = items
end
# The JSON needs to be decoded before it's sent to the `to_json` method in the home_controller otherwise the JSON will be escaped...
# What a mess!
def to_json
{
:user => ActiveSupport::JSON.decode(user.to_json(:only => :username, :methods => [:foo, :bar])),
:items => ActiveSupport::JSON.decode(auctions.to_json(:only => [:id, :name], :methods => [:zim, :gir]))
}
end
end
Look Ma! No more hacks!
Override as_json instead
The ActiveRecord::Serialization#as_json docs are pretty sparse. Here's the brief:
as_json(options = nil)
[show source]
For more information on to_json vs as_json, see the accepted answer for Overriding to_json in Rails 2.3.5
The code sans hacks
user.rb
class User < ActiveRecord::Base
def as_json(options)
options = { :only => [:username], :methods => [:foo, :bar] }.merge(options)
super(options)
end
end
item.rb
class Item < ActiveRecord::Base
def as_json(options)
options = { :only => [:id, name], :methods => [:zim, :gir] }.merge(options)
super(options)
end
end
home_controller.rb
class HomeController < ApplicationController
def observe
#items = Items.find(...)
respond_to do |format|
format.js do
render :json => {
:user => current_user || {},
:items => #items
}
end
end
end
end
EDITED to use as_json instead of to_json. See How to override to_json in Rails? for a detailed explanation. I think this is the best answer.
You can render the JSON you want in the controller without the need for the helper model.
def observe
respond_to do |format|
format.js do
render :json => {
:user => current_user.as_json(:only => [:username], :methods => [:foo, :bar]),
:items => #items.collect{ |i| i.as_json(:only => [:id, :name], :methods => [:zim, :gir]) }
}
end
end
end
Make sure ActiveRecord::Base.include_root_in_json is set to false or else you'll get a 'user' attribute inside of 'user'. Unfortunately, it looks like Arrays do not pass options down to each element, so the collect is necessary.
Incase anyone is looking for an alternative solution for this, this is how I solved this in Rails 4.2:
def observe
#item = some_item
#user = some_user
respond_to do |format|
format.js do
serialized_item = ItemSerializer.new(#item).attributes
serialized_user = UserSerializer.new(#user).attributes
render :json => {
:item => serialized_item,
:user => serialized_user
}
end
end
end
This returns the serialized version of both objects as JSON, accessible via response.user and response.item.
There are a lot of new Gems for building JSON now, for this case the most suitable I have found is Jsonify:
https://github.com/bsiggelkow/jsonify
https://github.com/bsiggelkow/jsonify-rails
This allows you to build up the mix of attributes and arrays from your models.
Working answer #2 To avoid the issue of your json being "escaped", build up the data structure by hand, then call to_json on it once. It can get a little wordy, but you can do it all in the controller, or abstract it out to the individual models as to_hash or something.
def observe
respond_to do |format|
format.js do
render :json => {
:user => {:username => current_user.username, :foo => current_user.foo, :bar => current_user.bar},
:items => #items.collect{ |i| {:id => i.id, :name => i.name, :zim => i.zim, :gir => i.gir} }
}
end
end
end

Resources