I am a rails beginner,now I follow the guide and create a test project as below:
admin_controller.rb
def blogEdit
#btnName = "更新"
#submitURL = "blogUpdate"
#blog=Blog.find(params[:id])
end
def blogUpdate
#blog=Blog.find(params[:id])
#blog.update(params[:blog])
redirect_to "/admin/blogList"
end
blogEdit.slim
= render "blogForm"
_blogForm.slim
=form_for :blog,url:{action: #submitURL} do |f|
=f.label :title ,"标题"
=f.text_field :title
=f.label :body ,"内容"
=f.text_area :body,rows:10,style:'width:500px;'
=f.label :author ,"作者"
=f.text_field :author
=f.hidden_field :id
=f.submit #btnName,class:'btn'
but when I update a blog,I get the error:
Couldn't find Blog without an ID
Rails.root: /home/hxh/share/ruby/myblog
Application Trace | Framework Trace | Full Trace
app/controllers/admin_controller.rb:28:in `blogUpdate'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"3SBp+xSft8SzA5poczxNSU1zKReO2OpZQqgESn6ZURs=",
"blog"=>{"title"=>"违法的",
"body"=>"的说法都是",
"author"=>"似懂非懂",
"id"=>"2"},
"commit"=>"更新"}
so It shows that I not get the params[:id]
#blog=Blog.find(params[:id])
I had follow the guide,why I got the result?
You're passing =f.hidden_field :id into form. So your id in in params[:blog][:id].
But id have to be passing via #submitURL. Judging by your params #submitUrl aren't contains id of the blog.
You are not following guide convensions or your guide doesn't do it. Because there is common rules for name actions. It's edit and update instead your blogEdit and blogUpdate.
The url that is created while submitting is not resourceful instead the URL should be created like /2/blogupdate but in your case it is /blogupdate.
What you can do here change these lines
def blogEdit
#btnName = "更新"
#submitURL = "/#{params[:id]}/blogUpdate"
#blog=Blog.find(params[:id])
end
In your form, instead of #submitURL, why don't you use blogUpdate_blog_path(#blog)?
Related
I'm using the acts-as-taggable-on gem to add tags on my business + service models in my Rails 6 app in order to enable users to find whatever service/business they're seeking more easily. A service will have food-specific tags available if the business is a restaurant, and more general ones if it's another type of business. Whenever I try to create either a new business or a new service, I'm getting the same error:
ActiveRecord::AssociationTypeMismatch - ActsAsTaggableOn::Tag(#755220) expected, got "" which is an instance of String(#7280):
app/controllers/services_controller.rb:12:in `create'
Here is the relevant part of my Service model code:
class Service < ApplicationRecord
belongs_to :business
acts_as_taggable_on :tags
acts_as_taggable_on :food_taggings, :service_taggings
Here's the relevant part of my new service form:
<%= simple_form_for [#business, #service] do |f|%>
<%= f.input :food_taggings, collection: Service.foodlist, input_html: {multiple: true, id: "food_tagging_new", class: "select2"}, label: "Please add some descriptive tags to the dish that you're offering so that local users could find it more easily" %>
<%= f.input :service_taggings, collection: Business.offerings, input_html: {multiple: true, id: "service_tagging_new", class: "select2"}, label: "Please add some descriptive tags to the service that you're offering so that local users could find it more easily" %>
<%= f.button :submit, 'Submit', class: 'btn btn-primary'%>
</div>
<% end %>
Here's the relevant part of my services controller code:
def create
#service = Service.new(service_params)
if #service.save
flash[:notice] = "This service was successfully added!"
redirect_to #service
else
render "new"
end
end
private
def service_params
params.require(:service).permit( :tag_list, tag_list: [], food_taggings: [], service_taggings: [] )
end
And here are the params that go along with the create new service request:
{"authenticity_token"=>"kld9sOSfro/nrINxQdKpXCZnxt6Cjb6TIw+jcjW5XmpUhvfm767dPXStOGB2vEBbckZvb87uKXlZo2KGjAo8vA==", "service"=>{"name"=>"", "description"=>"", "price_cents"=>"", "food"=>"0", "food_taggings"=>[""], "service_taggings"=>[""]}, "commit"=>"Submit", "controller"=>"services", "action"=>"create", "business_id"=>"5"}
How would I go about fixing this issue so that both of the models can get created successfully? I was already able to create some seeds for both without including any tags successfully, and I'm not sure exactly what Rails is expecting now?
To recap what acts_as_taggable_on is doing, say you declare a single taggable attribute:
class Service < ApplicationRecord
acts_as_taggable_on :tags
end
The gem does two things:
It dynamically creates an association (i.e., has_many/belongs_to) between your class and ActsAsTaggableOn::Tag, which is how the gem models tags you define. When you access tags on a instance of Service, you get an array of these Tag objects like you would with any has_many association.
It also creates a friendly convenience wrapper called tag_list (note: singluarized), which is the main way the gem expects you to interact with tags. Calling this will do the work of querying the associated Tag objects and return you a nice array of strings. Or you can assign it an array of strings, which get parsed into Tag objects.
In your form and controller, you are using the raw association references (food_taggings and service_taggings). Thus when your form POSTs, Rails properly raises an error because it is expecting those parameters to be arrays of Tag objects not arrays of strings.
If you change your form to use the convenience wrapper names for the form fields, the gem will properly parse the array of strings in your params and create the associated objects:
<%= simple_form_for [#business, #service] do |f|%>
<%= f.input :food_tagging_list, ... %>
<%= f.input :service_tagging_list, ... %>
...
<% end %>
Don't forget to alter your permitted parameters on the controller as well:
def service_params
params.require(:service).permit(tag_list: [],
food_tagging_list: [],
service_tagging_list: [])
end
Try adding include_blank: false in your form input options. I think Rails gives you this error because it will always send "" (empty string) from the taggings form.
<%= f.input :food_taggings,
collection: Service.foodlist,
input_html: {
multiple: true,
id: "food_tagging_new",
class: "select2"
},
include_blank: false # Add this
label: "Please add some descriptive tags to the dish that you're offering so that local users could find it more easily"
%>
I am using the administration framework 'active admin' to build a RubyOnRails Application. I have a Dropdown Menu that looks like this in my form:
form do |f|
f.inputs do
f.input :art, as: :select, :include_blank => "Bitte wählen!", label: 'art', :collection => ["Fachobjekt", "Gruppe", "Externes Dokument"]
end
f.actions
end
Now in my controller I want to check the selected value. I tried:
controller do
after_save :update_object
def update_object(guid)
if params[:art].values == 'Fachobjekt'
# do stuff
end
end
end
I chose 'Fachobjekt' in my Dropdown but I get the NoMethodeError "undefined method 'values' for nil:NilClass", so the params[:art] is null.
My question is: what is the correct syntax to get the selected value of my f.input-field? I appreciate any hint!
[:root_tables] ist the model name. I put it before [:art] like params[:root_tables][:art] but same error.
Put a
raise params.inspect
just before the
if params[:art]
line. You should be able to identify where the posted information resides in the params.
Thanks for all of your hints! Especially the hints for debugging with binding.pry helped me. I use f.select instead of f.input now:
form do |f|
f.inputs do
f.select :art, ["Fachobjekt", "Gruppe", "Externes Dokument"], :prompt => 'Bitte wählen! '
end
f.actions
end
For select the params[:root_table][:art] is working:
controller do
after_save :update_object
def update_object(guid)
if params[:root_table][:art] == 'Fachobjekt'
# do stuff
end
end
end
When i use f.input, [:art] is not even showing up when doing binding.pry or raise params.inspect.
With f.select it does.
However, at least it's working now, so thanks!
Issue: Instead of updating nested attributes, they are being created on top of the existing nested attributes when I hit the #update action of the associated features_controller.rb
Likely Cause: I think the problem lies in my lack of understanding in Rails' form_for. I think the breakdown is in my views, how I render the persisting nested attributes, and/or how I fail to specify the nested attribute's id, causing it to simply create a new one
feature.rb
class Feature < ActiveRecord::Base
...
has_many :scenarios
accepts_nested_attributes_for :scenarios,
allow_destroy: true,
reject_if: :all_blank
...
end
features_controller.rb
def update
...
project = Project.find(params[:project_id])
#feature = Feature.find(params[:id])
if #feature.update_attributes(feature_params)
# checking feature_params looks good...
# feature_params['scenarios'] => { <correct object hash> }
redirect_to project
else
render :edit
end
end
...
private
def feature_params
params.require(:feature).permit(:title, :narrative, :price, :eta, scenarios_attributes[:description, :_destroy])
end
_form.html.haml (simplified)
= form_for [#project, #feature] do |f|
...
- if #feature.new_record? -# if we are creating new feature
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
- else -# if we are editing an existing feature
= f.fields_for :scenarios do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
I'm sure there's a nicer way to achieve the if #feature.new_record? check. I'm also using a few Javascript hooks to create dynamic nested attribute forms (which I've left out), heavily influenced by Railscast #196 Nested Model Form (revised)
I would love a really nice Rails-y implementation of dealing with these sorts of nested forms.
Try adding :id to the :scenario_attributes portion of your feature_params method. You only have the description field and the ability to allow a destroy.
def feature_params
# added => before nested attributes
params.require(:feature).permit(:id, :title, :narrative, :price, :eta, scenarios_attributes => [:id, :description, :_destroy])
end
As #vinodadhikary suggested, you no longer need to check if feature is a new record, since Rails, specifically using the form_for method, will do that for you.
Update:
You don't need to define if #feature.new_record? ... else in your form. It will be taken care by Rails when you use form_for. Rails checks if the action is going to be create or update based on object.persisted?, so, you can update your form to:
= form_for [#project, #feature] do |f|
...
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
As #Philip7899 mentioned as a comment in the accepted answer, allowing the user to set the id means that they could "steal" children records belonging to another user.
However, Rails accepts_nested_attributes_for actually checks the id and raises:
ActiveRecord::RecordNotFound:
Couldn't find Answer with ID=5 for Questionnaire with ID=5
Basically the ids are looked for in the children association (again, as said by #glampr). Therefor, the child record belonging to another user is not found.
Ultimately, 401 is the response status (unlike the usual 404 from ActiveRecord::RecordNotFound)
Follows some code I used to test the behaviour.
let :params do
{
id: questionnaire.id,
questionnaire: {
participation_id: participation.id,
answers_attributes: answers_attributes
}
}
end
let :evil_params do
params.tap do |params|
params[:questionnaire][:answers_attributes]['0']['id'] = another_participant_s_answer.id.to_s
end
end
it "doesn't mess with other people's answers" do
old_value = another_participant_s_answer.value
put :update, evil_params
expect(another_participant_s_answer.reload.value).to eq(old_value) # pass
expect(response.status).to eq(401) # pass
end
In conclusion, adding the id to the permitted params as stated above is correct and safe.
Fascinating Rails.
Given
<% #incidents.each_with_index do |incident,i| %>
I can't figure out how to in place edit attributes on incident and parent associations such as incident.user or incident.contact
This works for example:
best_in_place incident, :notes, type: :input, nil: 'Add Note'
But I can't figure out how to do incident.customer to get a drop down of Customer.all (incident belongs_to :customer)
I get various errors each way I try it.
If I understand you correctly, in your controller's show action, or wherever's relevant:
#customer = Customer.all.map { |c| [c.id, c.customer_name] } # or whatever the customer name attribute is
In your view:
= best_in_place incident, :notes, :type => :select, :collection => #customer
This produces the [[a,b], [c,d]] format that the docs say is needed.
It would be less wordy with Customer.pluck(:id, :name) but that's only in Edge Rails at the time of writing (link to guides).
I am still struggling both writing the controller and the actual form to be able to nest one form in another with an optional model?
I have Message which has many contacts
When submitting a message, I want to add a contact optionally.
I have this as an example:
= simple_form_for Message.new, :remote => true do |f|
#message_form
= f.error_messages
%p
= f.input :account_name, :url => autocomplete_account_name_messages_path, :size => 40, :as => :autocomplete
%p
= f.input :topic, :required => true,
:input_html => {:size => 30}
#add_contact_btn
= link_to "Add Contact"
#contact_form
= f.simple_fields_for :contactd do |fc|
= fc.input :email
= fc.input :first_name
= fc.input :last_name
= f.submit 'Give'
= f.submit 'Request'
For Message.rb model, I have the following:
has_many :contacts
accepts_nested_attributes_for :contacts, :reject_if =>:all_blank
Note: When I used :contacts in the simple_fields_for it didn't work, so it is singular. But the reverse for accepts_nested_attributess_for.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Here is what I see passed as form data from google chrome:
message%5Baccount_name%5D:McKesson
message%5Btopic%5D:testing a contact
message%5Bbody%5D:testing this
sender_id:
receiver_id:23
message%5Bcontacts%5D%5Bemail%5D:888#gmail.com
message%5Bcontacts%5D%5Bfirst_name%5D:Ang
message%5Bcontacts%5D%5Blast_name%5D:Name
The correct method name is simple_fields_for (notice the plural)
Also, you need to keep the f. to call it on the simple_form object
I have a small project where I demonstrate how to use nested forms in simple-form, combined with cocoon (a gem I created to add/remove nested elements dynamically).
The project is on github.
Hope this helps.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Make sure you put in your Message.rb model the ability for it to accept the attributes too.
class Message < ActiveRecord::Base
attr_accessible :contacts_attributes
has_many :contacts
accepts_nested_attributes_for :contacts
I know it doesn't answer your question fully but it may have just been this. When it comes to my project, it would return nil if i didn't include the :contacts_attributes, in my case it deals with products. Hope this helps even if I'm not using simple form as of now!
I faced similar issues working with nested forms. As suggested by JustinRoR you need to define
attr_accessible: contacts_attributes.
You should be able to test the hash in the ruby console ( I am not sure if you have tried this). I suggest you to print the params[:message] and use this to create message from console like Message.new(params[:message]). (Note params[:message] is what you get by printing the params[:message] hash).
Once it works in console it should work like charm