accepts_nested_attributes_for for edit api in rails - ruby-on-rails

I have a paper model which has many questions. I am making api.
In paper.rb
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions, reject_if: :all_blank,allow_destroy: true
In question.rb
belongs_to :paper
In paper_controller.rb
def create
paper = current_user.papers.create(paper_params)
unless plan.blank?
render json: {status: 'successful', paper: paper }, status: 201
else
render json: {error: 'Some thing went wrong'}, status: 401
end
end
def update
paper = Paper.find(params[:id])
if paper.update_attributes(paper_params)
render json: {status: 'successful', paper: paper }, status: 201
else
render json: {error: 'Some thing went wrong'}, status: 401
end
end
private
def paper_params
params.require(:paper).permit(:subject,questions_attributes: [:id, :question])
end
My problem is whenever I edit questions in a specific paper questions are not edited but new question are formed. For example If Have 3 questions and I have send request for edit then there will be 6 questions for that paper.

UPDATE
For JSON there is a known issue. More info here.
If new questions are being formed it means your controller cannot find the existing child questions. The only way that could happen is if you are not sending the :id for each question, so the problem is not your controller or model, but what the form is sending from the client side.
If your form is using javascript, and submitting a json body, you should check also that the JSON structure has a child called "questions_attributes" and not "question_attributes".
FYI you do not need to actually specify the question :id in the permit code if your id's are integers (although you of course do have to send it for the update to work).
def paper_params
params.require(:paper).permit(:id, :subject, questions_attributes: [:question])
end
See the official docs example here. If your id's are not integers (eg. custom, such as GUIDS, then you do need it).

Related

Problem with creating two objects with one controller action with ruby on rails

I am trying to send one post to a create in a controller and create two objects. For some reason the contoller only will create a company. I did a similair thing in sinatra and it worked. I know that the route is correct and so is the object the post sends.
Conrtoller:
def index
stocks = current_user.stocks
render json: stocks, include: ['company']
end
def create
company = Comapny.find_or_create_by(name: params["company"])
stock = current_user.stocks.create(stock_params)
render json: stock, status: :created
end
Params:
def stock_params
params.require(:stock).permit(:name, :price_purchased_at, :number, :info, :restaurant )
end
Serializer:
class StockSerializer < ActiveModel::Serializer
attributes :id, :name, :price_purchased_at, :info, :number, :company
belongs_to :user
belongs_to :company
end
I have tried changing the serializer and the params. I have also tried leaving out the company create line to see if it will create the stock but it still won't create a stock.
You ensure in your create that a company with the expected name exists. But you do not pass the found company to the stock creation method. Therefore, the stock creation fails with Company must exist.
Just change your create method to this:
def create
company = Company.find_or_create_by(name: params['company'])
stock = current_user.stocks.create!(stock_params.merge(company_id: company.id))
render json: stock, status: :created
end

Unable to add string to an array

I am trying to make a column in my Posts table which is an array. I want the array to store the current_user's email (or id/name etc...).
However, for some reason I cannot get the current_user's email to append to the array.
The array column is called claimed_users and the purpose of the column is to store the user_email of all users who have "claimed" the post. This way I can implement code to prevent them from claiming more than one post amongst other things.
I currently have this in my View:
<%= link_to "Claim This Post", { controller: :posts, action: :claim, post_id: #post.id, user_id: current_user.id },{ class: 'btn btn-default'} %>
And This for my action in PostsController:
def claim
#post = Post.find(params[:post_id])
#user = User.find(params[:user_id])
#post.increment!(:claim)
#post.claimed_users << current_user.id
if #post.claim < 99
redirect_to #post, notice: 'You have successfully claimed this post. You have 8 hours to post a response'
else
redirect_to #post, notice: 'Sorry, this post already has enough claims'
end
end
I added "serialize: claimed_users, Array" in my Posts model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :responses
has_many :top_responses
has_many :claims
serialize :claimed_users, Array
end
However, I still have no luck. When I look in the database using the Rails console I still get "claimed_users: nil".
Before, I was getting "claimed_users: "--- []\n"" but that changed when I added some code though I'm not exactly sure where as I have tried a lot of things so far and read a lot of SO questions and google results to no avail.
I explored the idea of creating a new Model/Controller for Claims/Claimed_Users but it doesn't make much sense when I think about it.
Any help is greatly appreciated! I'm still fairly new to programming.

Rails web API throwing of error

I am trying to create a web API that allows creation of FriendShip by email or phone_number.
class Api::FriendshipsController < Api::BaseController
respond_to :json
def create
friend = User.where("email = ? OR phone_number = ?", params[:emailOrPhone], params[:emailOrPhone]).first # create a friend by email or phone_number
if friend.valid? # check if the friend exists, if it does we create our new friendship
friendship = Friendship.new
friendship.user = current_user
friendship.friend = friend
if friendship.valid? # check if friendship is valid
friendship.save # if it is, we save and return a success JSON response
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
end
end
Here is my FriendShip model
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
validates_uniqueness_of :user_id, scope: :friend_id, :message => '%{friend_id} is already a friend'
validate :check_friend_and_user # prevent user trying to add him/herself as friend.
def check_friend_and_user
errors.add(:friend, "can't be the same as user") if user == friend
end
end
Whenever the uniqueness constrain is violated, an error missing interpolation argument :friend_id in "%{friend_id} is already a friend" ({:model=>"Friendship", :attribute=>"User", :value=>2} given) with error code 500
How do I make it not throw an error but instead proceed to give back a 'fail json response' with status code 400
I want the caller of this API to know that they are trying to add someone that is already a friend. Getting back a status code 500 and a bunch of html does not seems to uniquely identify it. So I would like to throw a error in the form of JSON and status 200
What you seem to be trying to do is determine if the friend is already associated to the User via the friendship class. Which you can simplify with a has_many :friendships association on the User object.
Also, the way you're looking up by email OR phone is IMO unnecessarily ambiguous and will be problematic if you want to separately track one or the other for other purposes. Which you seem to be wanting to do since you have them broken out into separate database columns. I think you could just put two form inputs email or phone number and only pass one along to the controller. If you must have only one then you could determine what the form submits in Javascript or something.
In that case, you'd be better off sending the type of identifier with your initial data from your form, so you can look for one or the other. So your form would explicitly send the column lookup identifier, e.g. the params would be equivalent to the ruby hash
{friendship: {email: "someone#somewhere.com"}}
With that in the params then you could do what your trying with this code
# assuming you're passing via a params hash that would look like
# one or the other of the following
# {friendship: {email: "john#doe.com"}}
# {friendship: {phone_number: "123-123-1234"}}
def create
if current_user.friendships.find_or_create_by(friendship_params)
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
protected
def friendship_params
require(:friendship).permit(:email, :phone_number)
end

Rails 4: Escape Not Found error for nested form

I need to escape NotFound exception for nested form associations, when there is one relation isn't found.
For example, i have
class User < ActiveRecord::Base
has_many :user_selection_criteria
accepts_nested_attributes_for :user_selection_criteria, :reject_if => lambda { |t| t['brand_id'].nil? }, :allow_destroy => true
end
and
if #user.update_attributes user_params
render
else
render json: #user.errors, status: :unprocessable_entity
end
Which updates attributes, params do permit this, and everything is ok.
I use nested form, say, with those attributes.
user_selection_criteria_attributes[0][id]
user_selection_criteria_attributes[0][brand_id]
user_selection_criteria_attributes[0][_destroy]
user_selection_criteria_attributes[1][id]
user_selection_criteria_attributes[1][brand_id]
user_selection_criteria_attributes[1][_destroy]
user_selection_criteria_attributes[2][id]
user_selection_criteria_attributes[2][brand_id]
user_selection_criteria_attributes[3][_destroy]
etc...
Everything is OK, when i:
Leave id blank - a new record is created
Use id of existing record - corresponding record is updated.
But when i use non-existing record id, for example when another user already deleted this record, i get an error Couldn't find UserSelectionCriterium with ID=13 for User with ID=12
When i use
begin
if #user.update_attributes user_params
render
else
render json: #user.errors, status: :unprocessable_entity
end
escape
render
end
Error is escaped, but attributes aren't saved. But that's expected.
Question: how do i squelch/escape that error, ignore that record does not exist any more, and save any other valid relations?
I.e. when nested relation with id 13 doesn't exist, but relation with id 14 exists, relation 13 is ignored and 14 is processed normally.
You can filter these ids which's record does not exsited, like this(ideally, use your own code):
def some_params
params.require(:user).permit(user_selection_criteria_attributes: [:id, :brand_id]).tap do |white_list|
white_list[:user_selection_criteria_attributes].each do |key, value|
if value[:id].present? && UserSelectionCriteria.find_by(id: value[:id]).blank?
white_list[:user_selection_criteria_attributes].delete(key)
end
end
end
end

accepts_nested_attributes_for alias?

In Topic model:
class Topic < ActiveRecord::Base
has_many :choices, :dependent => :destroy
accepts_nested_attributes_for :choices
attr_accessible :title, :choices
end
During a POST create, the params submitted is :choices, instead of :choices_attributes expected by Rails, and giving an error:
ActiveRecord::AssociationTypeMismatch (Choice(#70365943501680) expected,
got ActiveSupport::HashWithIndifferentAccess(#70365951899600)):
Is there a way to config accepts_nested_attributes_for to accept params passing as choices instead of choices_attributes in a JSON call?
Currently, I did the attributes creation in the controller (which seems not to be an elegant solution):
def create
choices = params[:topic].delete(:choices)
#topic = Topic.new(params[:topic])
if choices
choices.each do |choice|
#topic.choices.build(choice)
end
end
if #topic.save
render json: #topic, status: :created, location: #topic
else
render json: #topic.errors, status: :unprocessable_entity
end
end
This is an older question, but I just ran into the same problem. Is there any other way around this? It looks like that "_attributes" string is hardcoded in the nested_attributes.rb code (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L337).
Assigning "choices_attributes" to a property when submitting a form is fine, but what if it's being used for an API. In that case it just doesn't make sense.
Does anyone have a way around this or an alternative when passing JSON for an API?
Thanks.
UPDATE:
Well, since I haven't heard any updates on this I'm going to show how I'm getting around this right now. Being new to Rails, I'm open to suggestions, but this is the only way I can figure it out at the moment.
I created an adjust_for_nested_attributes method in my API base_controller.rb
def adjust_for_nested_attributes(attrs)
Array(attrs).each do |param|
if params[param].present?
params["#{param}_attributes"] = params[param]
params.delete(param)
end
end
end
This method basically converts any attributes that are passed in to #{attr}_attributes so that it works with accepts_nested_attributes_for.
Then in each controller that needs this functionality I added a before_action like so
before_action only: [:create] do
adjust_for_nested_attributes(:choices)
end
Right now I'm only worried about creation, but if you needed it for update you could add that into the 'only' clause of the before_action.
You can create method choices= in model as
def choices=(params)
self.choices_attributes = params
end
But you'll break your setter for choices association.
The best way is to modify your form to return choices_attributes instead choices
# Adds support for creating choices associations via `choices=value`
# This is in addition to `choices_attributes=value` method provided by
# `accepts_nested_attributes_for :choices`
def choices=(value)
value.is_a?(Array) && value.first.is_a?(Hash) ? (self.choices_attributes = value) : super
end

Resources