Form parameter isn't saving properly in Rails - ruby-on-rails

So, I've got this action in order to save a model:
def create
logger.debug(params[:user_id])
group_id = params['approver']['group_id']
#approver = Approver.new(params[:approver])
#approver.user_id = params[:user_id]
respond_to do |format|
if #approver.save
logger.warn("Approver saved!")
flash[:notice] = "New approver has been added!"
format.html { redirect_to(group_path(group_id)) }
else
flash[:notice] = "Sorry .. had issues adding the approvers!"
format.html { redirect_to(group_path(group_id)) }
end
end
end
Parameters being passed in are:
Parameters: {"commit"=>"Submit", "authenticity_token"=>"C35lovRRjJzekruZiwTZjaMs4KgwiEJnXn10b0nD+0w=", "utf8"=>"✓", "user_id"=>["18"], "approver"=>{"group_id"=>"13"}}
And looking at my logs, the debug message in the action prints '13' as the correct value. However, the value being inserted into the database is always '1' and here's the snippet from the logs:
INSERT INTO `approvers` (`user_id`, `updated_at`, `created_at`, `group_id`) VALUES (1, '2011-07-13 04:58:51', '2011-07-13 04:58:51', 13)
To further complicate matters, in order to debug, if I change line 5 of the action to:
#approver.user_id = 19
it all works fine.
Can anyone explain what I'm doing wrong?

The group_id isn't your problem, that is going through fine. Your problem is this:
"user_id"=>["18"]
So params[:user_id] is actually an array but you're treating it like an ID. Then, someone inside Rails is converting that array to 1. When you say this:
#approver.user_id = 19
Everything works because you're assigning a Fixnum to user_id and that's what user_id expects.
You need to figure out why you're getting an array user_id and then fix that. Or, figure out what you should do with an array, maybe #approver.user_id = params[:user_id][0] makes sense, maybe not.

Related

rails: nested form attributes how to avoid creating new entries in the database?

I have a model Books and a model Authors.
The form for adding books, contains a nested for allowing to add authors. That works. However, I have an autocomplete function on the authors fields, so when the form is posted to the controller, the author (almost) for sure exists in the database.
I should somehow do a find_or_initialize_by on the nested attributed.
I'm maybe looking at the wrong place, but I can't find this in the rails guides. I tried this (found on SO):
def create
#book = Book.new(params_book)
small_name = params[:book][:authors_attributes]["0"]["name"].downcase
aut_id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join
#book.authors = Author.find_or_initialize_by(id: aut_id)
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
This creates an error:
undefined method `each' for #<Author:0x007fac59c7e1a8>
referring to the line #book.authors = Author.find_or_initialize_by(id: aut_id)
EDIT
After the comments on this question, I updated the code to this:
def create
book_params = params_book
small_name = params[:book][:authors_attributes]["0"]["name"].downcase
id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join
book_params["authors_attributes"]["0"]["id"] = id
#book = Book.new(book_params)
if #book.save
redirect_to see_book_url(Biblio.last)
else
....
The book params look like this:
<ActionController::Parameters {"title"=>"Testus Testa",
"authors_attributes"=><ActionController::Parameters {
"0"=><ActionController::Parameters {"name"=>"Vabien", "id"=>"22"}
permitted: true>} permitted: true>} permitted: true>
That looks fine to me, BUT, I get this error:
ActiveRecord::RecordNotFound in Administration::BooksController#create
Couldn't find Author with ID=22 for Book with ID=
Ok so the easiest way to get what you want is to change autocomplete in your form from an array of names like: ['author 1 name', 'author 2 name'] change it to an array of objects containing the name and id of the author like: [{label: 'author 1 name', value: 0}, {label: 'author 2 name', value: 1}] so then as long as that form field is now for "id" instead of "name" then in your controller all you have to do is:
def create
#book = Book.new(params_book)
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
Because only attributes without an ID will be created as new objects. Just make sure you set accepts_nested_attributes_for :authors in your Book model.
The error you are getting is because #book.authors is a many relationship so it expects a collection when you set it not an individual author. To add an individual author to the collection you do #book.authors << Author.find_or_initialize_by(id: aut_id) instead of #book.authors = Author.find_or_initialize_by(id: aut_id) although its redundant to fetch the id using the name just to initialize with an id. The id will be created automatically. Use Author.find_or_initialize_by(name: small_name) instead.
In your current code you have multiple authors being created not only due to the lack of "id" being used but because #book = Book.new(params_book) passes the nested attributes to the object initializer and then after you are accessing the nested attribute params and adding authors again. Also if you have multiple authors with the same name then Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join would actually make an ID out of the combined ID of all authors with that name.
If you want to do it manually then remove :authors_attributes from your permit in "params_book" method so it won't be passed to Book.new then do the following:
def create
#book = Book.new(params_book)
params[:book][:author_attributes].each{|k,v| #book.authors << Author.find_or_initialize_by(name: v['name'])}
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
Let me know if you have trouble!
After response from poster
remove :authors_attributes from your permit in "params_book" method and try this:
def create
#book = Book.new(params_book)
#book.authors_attributes = params[:book][:author_attributes].inject({}){|hash,(k,v)| hash[k] = Author.find_or_initialize_by(name: v['name']).attributes.merge(v) and hash}
if #book.save
redirect_to see_book_url(Book.last)
else
render 'new'
end
end
Solved, thanks a lot to Jose Castellanos and this post:
Adding existing has_many records to new record with accepts_nested_attributes_for
The code now is:
# the strong params isn't a Hash, so this is necessary
# to manipulate data in params :
book_params = params_book
# All registrations in the DB are small case
small_name = params[:book][:authors_attributes]["0"]["name"].downcase
# the form sends the author's name, but I need to test against the id:
id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join
book_params["authors_attributes"]["0"]["name"] = params[:book][:authors_attributes]["0"]["name"].downcase
# this author_ids is the line that I was missing! necessary to
# test whether the author already exists and avoids adding a
# new identical author to the DB.
book_params["author_ids"] = id
book_params["authors_attributes"]["0"]["id"] = id
# the rest is pretty standard:
#book = Book.new(book_params)
if #book.save
redirect_to see_book_url(Book.last)
else

Could not find record error while deleting record in Rails

I'm trying to delete a record by passing id of that record. The code looks like this:
def destroy_catalogue_entry
#catalogue_entry = CatalogueEntry.find(params[:catalogue_entry_id])
if #catalogue_entry.destroy
flash[:success] = 'Catalogue entry deleted successfully.'
else
flash[:error] = 'Failed...'
end
end
I'm getting an interesting error. When my function destroy_catalogue_entry is called it shows:
Couldn't find CatalogueEntry with 'id'=16
but as I comment If condition section and render #catalogue_entry as json, the output is printed successfully. So how is it possible? Am I making some silly mistake or is there logical reason. Please enlighten me.
Solved! All I did is this:
def destroy_catalogue_entry
#catalogue_entry = CatalogueEntry.find(params[:catalogue_entry_id])
if #catalogue_entry.destroy
flash[:success] = 'Catalogue entry deleted Successfully'
redirect_to action: :view_catalogue_entries, dc_id: #catalogue_entry.dc_id
else
flash[:success] = 'Failed...'
end
end
When I notice the console, the record was getting deleted successfully but after that there was a SELECT query for the same record, that is why it was throwing the error Couldn't find CatalogueEntry with 'id'=16. As I redirected it, the problem was solved.
I think destroy method is returning an object. In ruby anything other than false or null will be taken to true in if statement. You can do puts on destroy method and see what its returning.
i presume your,
#catalogue_entry = CatalogueEntry.find(params[:catalogue_entry_id])
is returning that error because it cant find CatalogueEntry with id 6, make sure you have CatalogueEntry with that id.

Take random ids, then store those random ids into the db

so I'm working on a code snippet that essentially takes out 35 random ids from the table List.
What I would like to do to find the ids that got randomly generated, store them into a database called Status.
The purpose is to avoid duplication the next time I get a new 35 random ids from the List. So I never get the same random id twice.
Here's what I've tried, but been unsuccessful to get working.
#schedule = current_user.schedules.new
if #schedule.save
#user = User.find(params[:id])
Resque.enqueue(ScheduleTweets, #user.token)
#schedule.update_attribute(:trial, true)
flash[:notice] = "success"
redirect_to :back
else
flash[:alert] = "Try again."
redirect_to :back
end
and the worker:
def self.perform(user_token)
list = List.first(6)
#status = list.statuses.create
list.each do |list|
Status.create(list_id: "#{list}")
if list.avatar.present?
client.create_update(body: {text: "#{list.text}", profile_ids: profile_ids, media: { 'thumbnail' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png', 'photo' => 'http://png-1.findicons.com/files/icons/85/kids/128/thumbnail.png' } })
end
end
end
however the Status.create(list_id: #list) doesn't work.
Does anybody have any idea what is going on, and how I can make the list_ids get saved successfully to Status?
It's also associated:
list has many statuses, and status belongs to list
The following line of code is wrong:
Status.create(list_id: "#{list}") # WRONG
In your case the list variable is a List instance. And you're passing its string version to list_id which expects an integer.
Do this:
Status.create(list_id: list.id)
Or this:
list.statuses.create
I think the following will also work:
Status.create(list: list)

Updating a HABTM model associations only deletes the associations

Creating new HABTM associations work just fine for me, but each time I try to update them, all the associations get deleted.
This is how my update action looks like.
def update
params[:dish][:cuisine_ids] ||= []
params[:dish][:diet_ids] ||= []
#user = current_user
#dish = #user.dishes_selling.find_by(:id => params[:id])
#dish.cuisines = Cuisine.where(:id => params[:dish][:cuisine_ids]) ### (1) ###
#dish.diets = Diet.where(:id => params[:dish][:diet_ids]) ### (1) ###
if #dish
respond_to do |format|
if #dish.update(dish_params) ### (2) ###
format.html { redirect_to dish_path(#dish), notice: 'Dish was edited.' }
else
format.html { render action: 'edit' }
end
end
else
flash[:error] = "You can only edit your dish!"
render :index
end
end
My dish params looks like so
def dish_params
params.require(:dish).permit(:title, :desc, cuisine_ids: [:id], diet_ids: [:id])
end
If on edit I click on two checkboxes of cuisine, the logs while on step (2) reads this (Note only Delete and no update or Insert for cuisine_dishes table)
(0.1ms) begin transaction
SQL (0.4ms) DELETE FROM "cuisines_dishes" WHERE "cuisines_dishes"."dish_id" = ? AND "cuisines_dishes"."cuisine_id" IN (7, 9) [["dish_id", 61]]
SQL (0.2ms) UPDATE "dishes" SET "desc" = ?, "updated_at" = ? WHERE "dishes"."id" = 61 [["desc", "yema1245"], ["updated_at", "2014-06-12 06:57:36.622811"]]
(5.5ms) commit transaction
Redirected to http://localhost:3000/dishes/61
Completed 302 Found in 48ms (ActiveRecord: 13.7ms)
Question : Why does updating HABTM associations try to delete the association, instead of trying to create new ones if they do not previously exist, and delete if they previously existed but do not any more.
You should try using the << operator instead of = ;
#dish.cuisines << Cuisine.where(:id => params[:dish][:cuisine_ids]).last
This should add and not replace the required ids in both the tables.
I think the answer is born from a question I have:
Why does updating HABTM associations
What do you mean by updating HABTM associations?
HABTM
HABTM associations are a collection of records which are available to add (<<), or remove (.delete). You can't "update" a record with this - you either have to add or delete (mainly because they don't have primary keys.
We do that in this way:
#app/controllers/your_controller.rb
def update
#obj = Model.find params[:id]
#obj2 = Model2.find params[:model_2]
#obj.asssociative_data << #obj2
end
The problem you have is you're passing new data to your habtm association. The only way you can "update" this data is to overwrite all the current collection with the new ids you've posted (which is what I think you're doing)
Fix
The way to fix this will be to make sure you're passing the correct params in your cuisine_ids: [:id], diet_ids: [:id] strong_params method.
I think your problem will be that you're passing :id only. I think Rails expects you to pass :cuisine_ids and :diet_ids, with those arrays populated with numerical data only, like with our code:
def profile_params
params.require(:profile).permit(attributes(Profile), :image, :category_ids)
end
To do this, you'll have to do this with your form:
<%= f.select(:category_ids, Category.all, prompt: "Category") %>

Rails Activerecord: Update where conditions... else create

I need to check multiple columns of a table to see if I find a match. If I find a match I need to "updateattributes" of the matching record with all of my form params... Else I need to add a new record with all of my form params.
if #somethingtoupdate = Model.where("column1 = ? and column2 = ?", params[:something][:column1], params[:something][:column2])
if #somethingtoupdate = Model.update_attributes(params[:something])
redirect_to somewhere_path, :notice => "The existing record was updated"
else
render "myformlocation"
end
else
#added = Model.new(params[:something])
if #added.save
redirect_to somewhere_path, :notice => "The new record was created"
else
render "myformlocation"
end
end
Update
#somethingtoupdate = Model.where("this_id = ? and that_id = ?", params[:something][:this_id], params[:something][:that_id])
if ! #somethingtoupdate.empty?
if #somethingtoupdate.update_attributes(params[:something])
redirect_to some_path, :notice => "The existing record was updated"
else
render "myformlocation"
end
else
#added = Model.new(params[:something])
if #added.save
redirect_to some_path, :notice => "The new record was created"
else
render "myformlocation"
end
end
This is where I stand now thanks to #micahbf.
But, I am still getting an error on my "update_attributes" when there is a matching record.
Seems like this should work.... What am I missing or doing wrong?
This is because where does not return nil if it doesn't find anything, it returns an empty array, which is still truthy, so the block gets executed.
You can use empty? to check whether to run the block or not.
Note also that if it finds a match, the match will still be returned inside of an array (even if there was only one match). So you will have to do something like call first on the result to take the first returned model and update it.
So, the top might look like:
#somethingtoupdate = Model.where("column1 = ? and column2 = ?", params[:something][:column1], params[:something][:column2])
if ! #somethingtoupdate.empty?
if #somethingtoupdate.first.update_attributes(params[:something])
redirect_to some_path, :notice => "The existing record was updated"
else
render "myformlocation"
end
else
// do stuff if the query found no matches
end
I think here is short method to find record and if found then update record and if record not found then create it.
#somethingtoupdate = Model.where("column1 = ? and column2 = ?", params[:something][:column1], params[:something][:column2]).first_or_initialize
#somethingtoupdate.update_attributes(params[:something])
First of all, Model.update_attributes(params[:something]) is not working (at least in Rails 3.2.12). It should be #somethingtoupdate.update_attributes(params[:something]).
Also, there is an existing method for this kind of purpose: first_or_create.
#somethingtoupdate = Model.where("column1 = ? and column2 = ?", params[:something][:column1], params[:something][:column2]).first_or_create

Resources