Unpermitted parameters for an attribute that has an array of values - ruby-on-rails

This is what the parameters look like for my Node#update:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"F1HGLeI9E=", "node"=>{"name"=>"Lesty", "parents"=>["13", "35", ""], "family_tree_id"=>"2"}, "commit"=>"Update Node", "id"=>"38"}
This is my NodesController:
private
def node_params
params.require(:node).permit(:user_id, :family_tree_id, :name, :description, :parent_id, :parent, :parents)
end
But when I try to update the #node object that produces the above log, I get this error:
Node Load (1.1ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 38]]
Unpermitted parameters: parents
(1.7ms) BEGIN
(1.3ms) COMMIT
Redirected to http://localhost:3000/nodes/38
Completed 302 Found in 21ms (ActiveRecord: 4.0ms)
It is important to note that the parents attribute for my #node object is inherited via the gem ancestry and not directly on the Node model or via an association.
Thoughts?

You'll need to tell strong_parameters to expect an array if I recall correctly.
def node_params
params.require(:node).permit(:user_id, :family_tree_id, :name, :description, :parent_id, :parent, parents: [])
end

You can simply try this
private
def node_params
params.require(:node).permit(:name, :family_tree_id, parents: [])
end

Related

How to upload multiple files with carrierwave and create multiple records

I am trying to modify my image upload routine in order to upload multiple images at the same time. Currently I am able to upload multiple images, but it creates only one record. How can I achieve it that one submit creates multiple records?
I do not want to go through an association, but instead be able to directly upload multiple images.
I currently have a Model:
class Diapo < ApplicationRecord
belongs_to :album
mount_uploaders :file_name, DiapoUploader
end
My Controller:
class Admin::DiaposController < ApplicationController
def new
#diapo = Diapo.new
#page_title = 'Create new Diapo'
end
def create
#diapo = Diapo.new(diapo_params)
if #diapo.save
flash[:notice] = 'Diapo #{#diapo.file_name} was successfully created.
redirect_to action: :index
else
#page_title = 'Create new Diapo'
render action: :new
end
end
.
.
.
private
def diapo_params
params.require(:diapo).permit({file_name: []}, :title, :alt, :author, :copyright, :album_id)
end
end
in the form I have:
<%= f.file_field :file_name, multiple: true, class: 'form-field' %>
Currently I get one post
Started POST "/admin/diapos" for ::1 at 2019-12-18 10:45:19 +0100
Processing by Admin::DiaposController#create as HTML
Parameters: {"authenticity_token"=>"something==", "diapo"=>{"file_name"=>[#<ActionDispatch::Http::UploadedFile:0x00007f9062b51a40 #tempfile=#<Tempfile:/var/folders/yg/pfjwzpkx5wq9d27svk760h0h0000gn/T/RackMultipart20191218-2329-7kffxo.jpg>, #original_filename="1.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"diapo[file_name][]\"; filename=\"1.jpg\"\r\nContent-Type: image/jpeg\r\n">, #<ActionDispatch::Http::UploadedFile:0x00007f9062b519f0 #tempfile=#<Tempfile:/var/folders/yg/pfjwzpkx5wq9d27svk760h0h0000gn/T/RackMultipart20191218-2329-o5qzhe.jpg>, #original_filename="2.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"diapo[file_name][]\"; filename=\"2.jpg\"\r\nContent-Type: image/jpeg\r\n">], "alt"=>"Test", "author_ids"=>"", "album_id"=>"124", "copyright"=>"Test"}, "commit"=>"Save"}
Admin Load (0.4ms) SELECT 'admins'.* FROM 'admins' WHERE 'admins'.'id' = 1 ORDER BY 'admins'.'id' ASC LIMIT 1
Unpermitted parameter: :author_ids
(0.2ms) BEGIN
↳ app/controllers/admin/diapos_controller.rb:14:in `create'
Album Load (0.3ms) SELECT `albums`.* FROM `albums` WHERE `albums`.`id` = 124 LIMIT 1
↳ app/controllers/admin/diapos_controller.rb:14:in `create'
Diapo Create (0.2ms) INSERT INTO `diapos` (`file_name`, `alt`, `copyright`, `album_id`, `created_at`, `updated_at`) VALUES ('[\"1.jpg\", \"2.jpg\"]', 'Test', 'Test', 124, '2019-12-18 09:45:34.406410', '2019-12-18 09:45:34.406410')
↳ app/controllers/admin/diapos_controller.rb:14:in `create'
(6.5ms) COMMIT
↳ app/controllers/admin/diapos_controller.rb:14:in `create'
Redirected to http://localhost:3000/admin/diapos
Completed 302 Found in 15350ms (ActiveRecord: 7.5ms | Allocations: 75512)
and 1 record is created which has the array of filenames in the filename field.
I suspect I can do it either in the controller or through javascript? How would I do this?
Thank you in advance!
You have to implement your requirement with has_many relation.
To save multiple record you may have parent child relationship just like below.
Ex :-
Post is a parent table and save its images as post_attachments which is child table
class Post < ActiveRecord::Base
has_many :post_attachments
accepts_nested_attributes_for :post_attachments
end
class PostAttachment < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
belongs_to :post
end
this is how you can achieve to save multiple files for parent record
For more detail see this Article which may be helpful to you.
https://kolosek.com/carrierwave-upload-multiple-images/

ActiveRecord rollback in Rails

Rails 5.2
I have a partial:
views/authors/_add_author_comment.html/slim
= form_for :author_note, url: author_notes_url, method: :post do |f|
= f.hidden_field :author, value: #book.author
= f.text_area :comment
button.btn.btn-primary type="button"
= f.submit t('authors.show.submit_comment')
In my controllers/author_notes_controller.rb, I have:
def create
#author_note = AuthorNote.new(author: params[:author_note][:author], user_id: current_user.id, comment: params[:author_note][:comment])
#author_note.save
end
When the form displays (part of a larger view), and I fill the comment out, and click on "Submit Comment", the comment is not saved. In the console, I see the following:
Processing by AuthorNotesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Jju1cpsLjXLY/TaF9p/Zkh8JQ/+KajjxwQHgNU4tNU9bjL8BiZQ8xL3S7ske1KqflOPHVaB9UTWRvgxNqzLd7Q==", "author_note"=>{"author"=>"John Dow", "comment"=>"This is a test"}, "commit"=>"Save Note"}
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = '5' ORDER BY `users`.`id` ASC LIMIT 1
↳ app/controllers/author_notes_controller.rb:23
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = '5' LIMIT 1
↳ app/controllers/author_notes_controller.rb:23
(0.2ms) BEGIN
↳ app/controllers/author_notes_controller.rb:28
(0.1ms) ROLLBACK
↳ app/controllers/author_notes_controller.rb:28
No template found for AuthorNotesController#create, rendering head :no_content
Completed 204 No Content in 60ms (ActiveRecord: 10.6ms)
Why is it ActiveRecord rolling back, and not saving the note to the author_notes table?
Resolution:
the author_note.rb model, I had: belongs_to :book, I commented it out
You didn't include your AuthorNote model, there might be some validation constraints that prevents the author_note from being saved.
Your code also doesn't handle validation error, so you might want to do that. But you can simply check for errors like:
def create
#author_note = AuthorNote.new(author: params[:author_note][:author], user_id: current_user.id, comment: params[:author_note][:comment])
#author_note.save
# puts works too
logger.debug "author_note save error: #{#author_note.errors.full_messages.join(' ')}"
end
This is highly likely due to rails 5 making belongs_to association required by default.
What that means is from rails > 5, if you define belongs_to on a model and if the corresponding record is not present. In this case, book_id should be nil in the AuthorNote record. ActiveRecord will error it out and rollback the transaction.
To fix this, instead of commenting out the belongs_to relationship all together, you can make it optional (beacause, removing the relationship might break the system)
class AuthorNote < ApplicationRecord
belongs_to :book, optional: true
end
Use ! along with the save or create that will show the error message Object.save! or Object.create!
def create
#author_note = AuthorNote.new(author: params[:author_note][:author], user_id: current_user.id, comment: params[:author_note][:comment])
#author_note.save!
end
OR
def create
#author_note = AuthorNote.create!(author: params[:author_note][:author], user_id: current_user.id, comment: params[:author_note][:comment])
end

Strange has_many Association Behavior in Rails 4

I've got two tables, User and Allergy. These are connected via another table, UserAllergy. The models are as would be expected:
class User
has_many :user_allergies
has_many :allergies, through: :user_allergies
end
class UserAllergy
belongs_to :user
belongs_to :allergy
end
class Allergy
has_many :user_allergies
has_many :users, through :user_allergies
end
What I'm confused about is creating allergies from a multiple-valued collection_select in my User form.
I have the following field:
<%= f.collection_select :allergy_ids,
Allergy.all,
:id,
:name,
{},
{ class: 'form-control', multiple: true }
%>
This correctly inserts a key into my params like so if I selected the Allergies with ids 1 and 2:
{ user: { id: "1", allergy_ids: ["", "1", "2"] } }
When I create the user instantiated with #user = User.new( my_params ), the weird behavior occurs. Instead of inserting the provided allergy_ids into the join table, Rails does a query to get all current user_allergies for the user, then deletes all of the current user_allergies:
Started PATCH "/employees/regular_user" for 127.0.0.1 at 2015-06-18 22:08:30 -0400
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "user"=>{ "allergy_ids"=>["", "1", "2", "3"]}, "button"=>"", "id"=>"regular_user"}
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
(0.1ms) begin transaction
Allergy Load (0.1ms) SELECT "allergies".* FROM "allergies" INNER JOIN "user_allergies" ON "allergies"."id" = "user_allergies"."allergy_id" WHERE "user_allergies"."user_id" = ? [["user_id", 1]]
SQL (0.1ms) DELETE FROM "user_allergies" WHERE "user_allergies"."user_id" = ? AND "user_allergies"."allergy_id" = 1 [["user_id", 1]]
(27.4ms) commit transaction
Redirected to http://localhost:3000/employees/regular_user
Completed 302 Found in 32ms (ActiveRecord: 27.8ms)
Anyone knows what gives, or what I need to do to create allergies implicitly? I've tried accepts_nested_attributes_for and changing around the form to use fields_for.
So, I went and looked at code of mine that does a similar function. Here's what my create method looks like. This is creating a Student with assignment to Student Groups in a school setting (I didn't use "class" since Ruby wouldn't like that).
def create
#student = Student.new(student_params)
if #student.save
#student.student_groups = StudentGroup.where(id: params[:student][:student_group_ids])
flash[:success] = "Student was successfully created."
redirect_to #student
else
render 'new', notice: "Your student could not be created."
end
end
I completely ignore the Student Group IDs when creating the student_params, since I'm not using them for mass assignment.
Yes, one extra line of code. I'd be really interested to hear if there's a way to accomplish this via mass assignment.
You're missing one part of the puzzle which is the relation from Allergy to User.
class Allergy
has_many :user_allergies
has_many :users, through: :user_allergies
end
Just give the following code a try-
params.require(:user).permit(___, ____, {allergy_ids: []}, ____, ____)

Rails 4 CSV Import and setting a value to a key value

I'm a complete Rails n00b and i'm sure this is an easy thing to do but i'm having trouble. I would like to take the value of a key in my URL and set it to the :category_id of a record in my database as i import that record from csv.
i can get it to work by creating a category_id field in my csv file and using the following code to import the file
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
record = Manufacturer.where(:category_id => row[1], :name => row[0] ).first_or_create
record.save!
end
end
But that requires adding the category_id to the csv.. what I would like to do is something like
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
record = Manufacturer.where(:category_id => #category, :name => row[0] ).first_or_create
record.save!
end
end
where #category is set in the URL. Something like:
...localhost:3000/manufacturers/import_manufacturer/2?category_id=2
this saves my record but sets the category id to "null" - this is the server output:
Started POST "/manufacturers/import?category_id=2" for 127.0.0.1 at 2013-07-18 11:19:55 +0200
Processing by ManufacturersController#import as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"DuAW1pOnaJieBYN7qEQGortYMC74OtLT6tT/e1dKAiU=", "file"=>#<ActionDispatch::Http::UploadedFile:0x007f835e3cae40 #tempfile=#<File:/var/folders/9j/4hs3_7kx11x3h4gkcrrgkwhc0000gq/T/RackMultipart20130718-23913-k0z3a8>, #original_filename="citroen.csv", #content_type="text/csv", #headers="Content-Disposition: form-data; name=\"file\"; filename=\"citroen.csv\"\r\nContent-Type: text/csv\r\n">, "commit"=>"Import", "category_id"=>"2"}
Category Load (0.3ms) SELECT `categories`.* FROM `categories` WHERE `categories`.`id` = 2 LIMIT 1
Manufacturer Load (0.6ms) SELECT `manufacturers`.* FROM `manufacturers` WHERE `manufacturers`.`category_id` IS NULL AND `manufacturers`.`name` = 'CITROEN' ORDER BY `manufacturers`.`id` ASC LIMIT 1
(0.3ms) BEGIN
SQL (0.5ms) INSERT INTO `manufacturers` (`created_at`, `name`, `updated_at`) VALUES ('2013-07-18 09:19:55', 'CITROEN', '2013-07-18 09:19:55')
(0.5ms) COMMIT
(0.2ms) BEGIN
(0.1ms) COMMIT
Manufacturer Load (0.6ms) SELECT `manufacturers`.* FROM `manufacturers` WHERE `manufacturers`.`category_id` IS NULL AND `manufacturers`.`name` = 'CITROEN' ORDER BY `manufacturers`.`id` ASC LIMIT 1
(0.2ms) BEGIN
(0.1ms) COMMIT
Is it possible to pass a variable into the csv.foreach loop like this?
Thanks in advance and sorry if I'm asking a stupid question.
if the class method import is called in the import controller action, you can pass params[:category_id] as the 2nd parameter.
class ManufacturersController < ApplicationController
def import
Manufacturer.import(params[:file], params[:category_id])
end
end
class Manufacturer < ActiveRecord::Base
def self.import(file, category_id)
CSV.foreach(file.path, headers: true) do |row|
record = Manufacturer.where(
:category_id => category_id,
:name => row[0]
).first_or_create
end
end
end

Edit object with HABTM association modify database before validation with ActiveAdmin

I Having an issue when trying to update a model that has has_and_belongs_to_many association.
Let's say that Post has_and_belongs_to_many Tag, and Post validates the presence of title and Tags.
If I update Post, removing its title and tags, I get validation error in title and tags, ok.
But ActiveAdmin already removed the records that make association between Post and Tag, so, if I leave Post edit page, the post is left invalid on database, without tags.
Here my models:
class Tag < ActiveRecord::Base
attr_accessible :label
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
attr_accessible :content, :title, :tag_ids
has_and_belongs_to_many :tags
validates_presence_of :content, :title, :tags
end
ActiveAdmin.register Post do
form do |f|
f.inputs do
f.input :title
f.input :content
f.input :image
f.input :tags
end
f.buttons
end
end
I usign chosen-rails gem and it allows user to unselect all tags of post.
Summarizing, my problem is: ActiveAdmin updates relationships on database before perform model validations.
There a solution for this behavior or I doing something wrong?
Edit:
Here the request log when I trying to update post without title and tags:
Started PUT "/admin/posts/8" for 127.0.0.1 at 2013-04-01 10:32:07 -0300
Processing by Admin::PostsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"amSbLlP/rgDrNn/N8lgq/KEaRXK1fMPShZDwpZ0QIJ4=", "post"=>{"title"=>"", "content"=>"content", "tag_ids"=>["", ""]}, "commit"=>"Update Post", "id"=>"8"}
AdminUser Load (0.2ms) SELECT `admin_users`.* FROM `admin_users` WHERE `admin_users`.`id` = 1 LIMIT 1
Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
(0.1ms) BEGIN
SQL (12.3ms) DELETE FROM `posts_tags` WHERE `posts_tags`.`post_id` = 8 AND `posts_tags`.`tag_id` IN (1, 2)
(49.6ms) COMMIT
(0.1ms) BEGIN
(0.2ms) ROLLBACK
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
Tag Load (0.2ms) SELECT `tags`.* FROM `tags`
Rendered /home/rodrigo/.rvm/gems/ruby-1.9.3-p125#blog/gems/activeadmin-0.5.1/app/views/active_admin/resource/edit.html.arb (192.3ms)
Completed 200 OK in 276ms (Views: 194.8ms | ActiveRecord: 63.3ms)
EDIT 2:
Ok, I sure that ActiveAdmin has this bug.
Looking at ActiveRecord behaviour, I think that validation flow is broken using only model class. See this example:
1.9.3p125 :064 > post = Post.find(8)
Post Load (0.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
=> #<Post id: 8, title: "title", content: "content", created_at: "2013-03-27 13:13:20", updated_at: "2013-03-27 13:13:20", image: "extrato.bmp">
1.9.3p125 :065 > post.tags
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
=> [#<Tag id: 1, label: "tag", created_at: "2013-02-25 18:32:45", updated_at: "2013-02-25 18:32:45">, #<Tag id: 2, label: "new", created_at: "2013-02-25 18:32:50", updated_at: "2013-02-25 18:32:50">]
1.9.3p125 :066 > post.title = ""
=> ""
1.9.3p125 :067 > post.save #<<<<<<< It's invalid on title
=> false
1.9.3p125 :068 > post.tags = [] #<<<<<<< This shouldnt trigger database update
(0.3ms) BEGIN
SQL (0.5ms) DELETE FROM `posts_tags` WHERE `posts_tags`.`post_id` = 8 AND `posts_tags`.`tag_id` IN (1, 2)
(55.5ms) COMMIT
=> []
1.9.3p125 :069 > post.save #<<<<<<< It's invalid on title AND TAGS
(0.2ms) BEGIN
(0.2ms) ROLLBACK
=> false
1.9.3p125 :070 > post.reload
Post Load (0.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 8 LIMIT 1
=> #<Post id: 8, title: "title", content: "content", created_at: "2013-03-27 13:13:20", updated_at: "2013-03-27 13:13:20", image: "extrato.bmp">
1.9.3p125 :071 > post.valid? #<<<<<<< Now, I have this model in invalid state
Tag Load (0.6ms) SELECT `tags`.* FROM `tags` INNER JOIN `posts_tags` ON `tags`.`id` = `posts_tags`.`tag_id` WHERE `posts_tags`.`post_id` = 8
=> false
Has any way to update post attributes(including tags) and validate the model before doing any database update?
You can use this gem: https://github.com/MartinKoerner/deferred_associations
The deferred associations will fix this bug.
#Rodrigo I was able to reproduce your issue locally without active admin, the issue is actually that is one of the default operations when using HABTM relationships if you see [here][1]
[1]: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many it says:
collection=objects
Replaces the collection’s content by deleting and adding objects as appropriate.
So apparently you will need to override this operation
Here is an example:
Override ActiveRecord << operator on has_many :through relationship, to accept data for the join model
Let me know how can I help you
I'd propose to add a tmp variable and store values in it. Then you should move them to database if validation passed.
Here is an example:
Your models/article.rb:
class Article < ActiveRecord::Base
validate :author_presence
has_and_belongs_to_many :authors
attr_writer :tmp_author_ids
def tmp_author_ids
#tmp_author_ids || author_ids
end
def author_presence
if tmp_author_ids.reject(&:blank?).empty?
errors.add(:tmp_author_ids, 'Author is missing')
else
self.author_ids = tmp_author_ids
end
end
end
Your admin/article.rb, block form:
f.input :tmp_author_ids, as: :select, multiple: true, collection: Author.all, label: 'Authors'
That's it
So, for anyone having the same issue, I found a workaround:
You can define a before_remove in the has_and_belongs_to_many association and raise an exception that then you can catch in ActiveAdmin.
For this it would be
class Post < ActiveRecord::Base
attr_accessible :content, :title, :tag_ids
has_and_belongs_to_many :tags, before_remove: :check_something
validates_presence_of :content, :title, :tags
end
def check_something(agent)
if self.tags.size == 1
raise ActiveModel::MissingAttributeError.new 'something'
end
end
more info: https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
if you want, you can redefine you update action in active_admin to prevent saving empty tags, in something like this style
ActiveAdmin.register Post do
controller do
def update
if params[:post][:tag_ids] == ["", ""]
flash.now[:alert] = "You can't remove all tags"
render :edit
else
super
end
end
end
...
end
and i think this stuff from model can be deleted
attr_accessor :new_tag_ids
validate :validate_new_tags_ids
after_save :update_tags
def update_tags
self.tag_ids = #new_tag_ids if defined?(#new_tag_ids)
#new_tag_ids = nil
end
private
def validate_new_tags_ids
errors[:tags] << "can't be blank (2)" if #new_tag_ids.blank?
end

Resources