Duplicate entries on nested attributes with decent-exposure - ruby-on-rails

This is a re-edit of a previous post
I previously thought that this problem was cocoon related but now I don't think so because the following code doesn't even invoke cocoon
Every time I update my form that contains nested attributes the number of nested records doubles. From what I can gather this happens when the form is called because I immediately see an update before I do anything and the form is presented with with duplicate entries
I have the relevant code for my view in HAML below -
%h3 Household Members
= f.simple_fields_for :neighbors do |neighbor|
= render 'neighbor_fields', :f => neighbor
I am using decent-exposure with my controller so the controller looks like this:
class HouseholdsController < ApplicationController
expose(:households)
expose(:household, strategy: StrongParametersStrategy)
def create
if household.save
redirect_to households_path, notice: 'Household was successfully created.'
else
render 'new'
end
end
def update
if household.save
redirect_to households_path, notice: 'Household was successfully updated.'
else
render 'edit'
end
end
def destroy
household.destroy
redirect_to households_path, notice: 'Household deleted.'
end
How can I keep my nested attributes from doubling?

I've never used decent exposure before, but I have come across the same problem using nested forms, with and without cocoon, the cause was the same in both cases. It has to do with strong parameters, and not white listing the :id of the nested attribute.
I'm not sure I exactly get what you're trying to do, so I'll give a classic kind of posts/comments example. If you had a form for posts, and you wanted to dynamically add comment fields, the strong parameters in your controller would look something like this.
params.require(:post).permit(:content, comments_attributes: [:id, :content, :_destroy])
You need to white list :id, :_destroy, and whatever other attributes your nested field has. If there's no :id associated with the comment, then rails considers that it's a new comment and makes a new record for it. When you white list the :id, then rails knows it's an existing object, and then just updates it.

Related

Rails 5 - Acts as Taggable On gem - defined tag lists

I am trying to learn how to use the Acts as Taggable On gem with Rails 5.
I have models called Proposal and Randd::Field. I am trying to tag proposals with tags which are the :title attribute of the Randd::Field table.
My models have:
Proposal
class Proposal < ApplicationRecord
acts_as_taggable_on :randd_maturities, :randd_fields, :randd_purposes, :randd_activities
# acts_as_taggable
# acts_as_taggable_on :skills, :interests
Randd::Field
(no association on Proposal).
Proposal helper
module ProposalsHelper
include ActsAsTaggableOn::TagsHelper
In my proposal form, I try to add tags:
<%#= f.select :tag_list %>
<%#= f.input :randd_field_list, collection: #randd_fields, label_method: :title, include_blank: false %>
<%= f.text_field :randd_field_list, input_html: {value: f.object.randd_field_list.to_s} %>
In my proposal controller, I have whitelisted an array of randd_field_list (which should hold each of the tags entered via the form).
def proposal_params
params.require(:proposal).permit(:title, :randd_maturity_list, :randd_fields_list,
I can add tags via the console. I cannot get this to work in the proposal form itself. In the console I can do:
p = Proposal.first
p.randd_field_list = [Randd::Field.last.title, Randd::Field.first.title]
p.save
This works to add the title of the first and last Randd::Fields to the array of tags on the proposal.
However, I can't figure out how to achieve this in the form. I get no errors showing in the rails s console. I cant see how to figure this out.
The Acts as Taggable On gem documentation this tutorial for editing tags - it suggests adding an update method to the Randd::Fields controller so that the tag can be updated. Taking that advice, I've tried to add the similar actions to my Randd::FieldsController as:
def edit
end
def update
#randd_field_list = ActsAsTaggableOn::Randd::Field.find(params[:id])
respond_to do |format|
if #randd_field_list.update(randd_field_list_params)
format.html { redirect_to root_path, notice: 'Tag was successfully updated.' }
format.json { render :show, status: :ok, location: #randd_field_list.proposal }
else
format.html { render :edit }
format.json { render json: #tag.errors, status: :unprocessable_entity }
end
end
This does nothing. I'm not sure if its a problem that I don't have a Tags Controller (at all), or if this is the generic label used for all controllers that are the tagging object. Is there anything required in the Proposal controller itself to handle the creation and updating of tags (which for my case are the titles of instances in the Randd::Field model?
Can anyone see what I need to do in order to use the tagging functionality provided by this gem? If I can do it in the console, it follows that I should be able to do it in the code - but its entirely unclear to me as to how to go about implementing this.
def proposal_params
params.require(:proposal).permit(:title, randd_maturity_list: [], randd_field_list:[]
end
You need to permit list params as an array, and make the tag list field on form to pass an array instead of text

ActiveAdmin: load and use models from other engine doesn't work

I followed the guide in the wiki to use my models from my engine in my main-app with active admin.
The only thing I must change (that doesn't mentioned in the wiki) was this:
if defined?(ActiveAdmin)
ActiveAdmin.register Blog::Category do
end
end
I just added: Blog::.
In my engine "Blog" I added a model named "category" with a name:string attribute. But when I add one in active admin the field name wasn't saved in the database. My request parameters are:
{"utf8"=>"✓", "authenticity_token"=>"PzKDTcoJZ6Sy2tXgw9WSwXiR7aZp81lOtBvfD5Ec3F72H5L7MEMLjlOFgKWQBo2U4n9mPc7AgjcIS3MTIY2nZA==", "category"=>{"name"=>"asdasd"}, "commit"=>"Update Category", "id"=>"1"}
any ideas why it isn't saved in the databse?
When I create a new one, the record is created but without my input.
I ran into the same problem. I was able to get the resources to save properly by overriding the activeadmin controller actions (in e.g. app/admin/category.rb) like this
ActiveAdmin.register Blog::Category do
permit_params :the, :parameters, :for, :your, :resource
controller do
def create
#category = Blog::Category.new permitted_params[:category]
if #category.save
notice = 'Category was successfully created.'
redirect_to admin_blog_category_url(#category), notice: notice
else
render :new
end
end
end
end
I'm not sure exactly what's going on in the default case yet, but my guess is that the activeadmin controller action is not creating the object in the right namespace - that is, it does something like #category = Category.new rather than #category = Blog::Category.new. A similar override appears to be necessary for the :update action as well.

Add children to parent from form constructed via ajax Rails 4

I have the following form:
When the user selects a product from the dropdown, a ajax is triggered to find the inventory of the single product to append the details to a table.
The user can attach a product detail to the order.
Finally I get something like that:
{"utf8"=>"✓", "authenticity_token"=>"xmlzMouWp0QGUnpKeawQ8OCPJ/GlF2bp0kn97ra2Qyb7TgsCkXmJEGD1l/oZitn+VPVJRc8x79/kTUtgbbDr0A==", "order"=>{"customer_search"=>"", "customer_id"=>"2", "product_search"=>"", "order_detail"=>[{"product_id"=>"10", "product_detail_id"=>"13", "price_id"=>"12"}, {"product_id"=>"1", "product_detail_id"=>"8", "price_id"=>"11"}], "subtotal"=>"111990", "tax"=>"0", "comission"=>"0", "total"=>"111990"}, "product_list"=>"1", "button"=>""}
My code to create the order is working, but I can not add the details.
Orders controller
def create
# Creates the order removing the order details from the hash
#order = Order.create(order_params.except!(:order_detail))
# Set all the details into an empty array
order_details_attributes = order_params[:order_detail]
order_details_attributes.each do |order_detail_attributes|
# Fill the params with order_id and creates the detail
order_detail_attributes["order_id"] = #order.id
#order_detail = OrderDetail.create(order_detail_attributes)
end
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
def order_params
params.require(:order).permit(:customer_id, :subtotal, :tax, :comission, :total, :invoice, :shipping_id, :order_detail, order_details_attributes: [:product_id, :product_detail_id, :price_id])
end
I'm getting this error:
undefined method `delete' for nil:NilClass
order_details_attributes = order_params[:order].delete(:order_detail)
What could be bad? I really need help :(
Thanks!!
order_params doesn't have key :order, it only has keys you specified in permit method when defined order_params. Actually, you don't need to manually create children records, as Rails can do this automatically. Check this out: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
You just need to add accepts_nested_attributes_for :order_details in Order model, fill order_details_attributes param when creating an order (currently you fill order_detail, you need to fix you form for it to be order_details_attributes, because this is one of Rails conventions, or you can use fields_for helper for this). Then you just create Order in standard way, like #order = Order.new(order_params) and #order.save, and you'll get order and order details created together.
This is a really messy thing in Rails. I can only recommend you to read the link I posted and use Google to find some tutorials.
As to the error you get:
undefined method `delete' for nil:NilClass
There is no :order key in order_params. It is in params, but not in order_params, because you called require(:order). So order_params returns only the keys you specified in permit method. But :order_detail will be empty, as you didn't describe it as an array with certain keys (like you did for order_details_attributes).
So, your problem is that you tried to implement nested attributes, but you pass :order_detail instead of :order_details_attributes (hm, but you still have it in strong params) and try to create children relations manually. Don't do this, just use what Rails provides to you.
There are 2 ways:
You continue to use order_detail param. In this case you need to change order_params in controller to look like so:
params.require(:order).permit(:customer_id, :subtotal, :tax, :comission, :total, :invoice, :shipping_id, order_detail: [:product_id, :product_detail_id, :price_id])
(just replace order_details_attributes with order_detail)
Then instead of
order_details_attributes = order_params[:order].delete(:order_detail)
you do
order_details_attributes = order_params[:order_detail]
(you don't need delete here as order_params is a method that returns a hash)
And leave rest controller code as it is now. And you don't need nested attributes, as you don't use it (bad way).
You fully use nested attributes. I described in a comment below how to do this. You also need to tweak you jquery code to generate order_details_attributes instead of order_detail.

Give a detailed illustration of how the MVC works in Rails, and how the controller communicates with forms etc?

Perhaps this can even become a Community Wiki, but I would love a detailed description of how the controller works - or rather, how I can get it to do what I want it to do.
I understand the general structure of MVC and how the model stores the db structure, and the controller interacts with the db and passes info to the view.
However, I am puzzled (on a fundamental level) about how to accomplish simple tasks using my controller. I know that if I want to create a new record for a model/object, I just do object = Object.new(:name => "Object Name") in the Rails console.
But how on earth would I do that in the CRUD elements of the controller and why?
Please use a simple example - e.g. showing a user the balance of their bank account (I know there are many complexities surrounding this, but ignore them for the sake of this explanation). What would the model look like (just include: Name, Address, Transaction Type (Deposits/Withdrawals), Balance).
What would a view look like? What would the controller look like? Any choices you make (like using a form) please explain them. Why would you use a form, as opposed to a drop down menu and (in layman terms) how does the form or drop down menu interact with the controller? How do I get the info captured there to the db and why am I doing it that way?
I know this sounds like a lot to ask, but I have done RailsTutorial.org, watched many Railscasts, read the Rails guides, and read many other tutorials and still have some basic gaps in my understanding of the way Rails works and why.
Thanks in advance.
I don't know how much more help I can be, but I understand your pain having just come to rails myself. The article recommended by ghoppe, "Skinny Controller, Fat Model" explains the function of Ms Vs & Cs nicely. Seeing as that does not fully answer your question I will try to explain the mechanics of each structure.
Model
class Account < ActiveRecord::Base
belongs_to :user
validates_presence_of :address
def name # Account does not have a name field, but User does so I will make a name method for Account and feed it name of the user it belongs to.
user = self.user # Account gets the user method with the <belongs_to :user> association
# note: Rails expects Accounts to have a user_id field so it can perform the "magic" to associate Accounts with Users
if user.name
return user.name
else
return nil
end
end
end
The model describes your object. Like an object in any OOP language you want to put all of your object logic here. This includes the rails helpers for association(has_one, belongs_to, ...) and validation, as well as any other method or library you want the object to be able use throughout your Models Views and Controllers.
Controller
class AccountsController < ApplicationController
before_filter :name, :only => :edit, :destroy # #account.name will be executed before the edit or destroy method(action) can be invoked on #account. If the user who has the account has a name the action will execute.
def index # This is a RESTful action and is mapped by Rails by default to an HTTP GET request. Rails expects an index.html.erb or index.haml.erb or index.something in the Accounts view to map this action to.
#accounts = Account.all # #accounts is an instance variable and will be accessible in the view this action is mapped to.
end
def show
#account = Account.find(params[:id]) # params[:id] is passed to the controller from the view. The params hash is the primary tool form moving data from a form or URL into a controller. Anytime you click on the link_to the show or edit action of an object Rails will put that objects id in the params hash and call the appropriate action in that objects controller. If you click the show link on an account it will call this action. Now the instance variable in the view show.html.erb will hold a single account instead of an array
end
def new
#account = Account.new # This initializes a new account with all the fields set to blank unless you specified a default in your migration. This account has not been save to the db yet. It is ready for a user to fill in.
respond_to do |format| # Rails can automatically respond differently to different client request. If a client i.e browser wants HTML rails responds with HTML. If a client e.g. an API want XML Rails responds with XML.
format.html # new.html.erb #
format.xml { render :xml => #account }
end
end
def edit
#account = Account.find(params[:id]) # Same as show, but mapped to a different view
end
def create # Finally we have a POST. All the prior actions were GETs, but now we are saving some data to the db.
#account = Account.new(params[:account]) # The :account key is special. It is a hash of hashes. It is populated by the form fields in new.html.erb. To access a specific field such as address we say <params[:account][:address]> and whatever the user entered in the address field in the View is at out fingers in the Controller.
respond_to do |format|
if #account.save # If the validations pass and the account gets saved redirect to the show page of the new record, otherwise refresh/render the new page (hopefully showing what error caused the record to fail to save).
format.html { redirect_to(#account, :notice => 'Account was successfully created.') }
format.xml { render :xml => #account, :status => :created, :location => #account }
else
format.html { render :action => "new" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
def update # This is another of the seven RESTful Rails actions and results in a PUT request because you are updating an existing record
#account = Account.find(params[:id])
respond_to do |format|
if #account.update_attributes(params[:account])
format.js # Rails can also respond with JavaScript. Look up UJS. Rails 3 has made large improvements here.
format.html { redirect_to(#account, :notice => 'Account was successfully updated.') }
format.xml { head :ok }
else
format.js
format.html { render :action => "edit" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
def destroy # This results in a DELETE
#account = Account.find(params[:id])
#account.destroy # destroy is a more thourough delete and will check the options of this records associations and destroy the associated objects as well if they are dependant on this object. The option <:dependant => :destroy> is not set for this object's only association: User. The user this account belongs to will therefore survive the destruction of this account.
respond_to do |format|
format.html { redirect_to(accounts_url) }
format.xml { head :ok }
end
end
end
View
Hopefully you can draw your own logic from here. The view is designed to render information passed as instance vars from a controller to a client: browser, api, smart phone. As well as to pass information from a client to the controller via the params hash. No complicated logic should get performed in a view even though a view with erb has the capability to execute any ruby code.
If an example view would also be helpful I am happy to oblige.
The best description of what the controller is:
http://edgeguides.rubyonrails.org/action_controller_overview.html
http://edgeguides.rubyonrails.org/routing.html
The controller doesn't communicate with the Database. The controller talks to the model, which then communicate with the database.
When I was starting I found very useful to use scaffolding and just looking at what was created.
Do this:
rails generate scaffold Post name:string title:string content:text
Examine all files under the app/ folder. Examine the file config/routes
Then comment here your specific questions.
At first, I thought this question was far too broad, along the lines of "how do I program?" But after reading your comments, I see what you're getting at. You don't quite grasp how MVC works in Rails and are wondering where your code goes.
What you should strive for is a Skinny Controller and a Fat Model. Keep logic out of views. So in your example, you calculate the account balance in the Model, and pass that information along (using the controller) to the view.
For a concise explanation for beginners with sample code, I recommend this article over here.

Writing proper validation errors?

Update : Gutted entire question with more thorough description
Ok same question with different names.
In my model, I do validate the presence of.
class QuickFact < ActiveRecord::Base
belongs_to :organization
validates_presence_of :quick_fact, :content
But if either is blank, it errors out with :
Missing template organizations/_quick_fact_fields.erb
Here's the catch. I have a nested form model with dynamically addable parts to it. As followed from here :
http://railscasts.com/episodes/197-nested-model-form-part-2
That is what generates and calls the _quick_fact_fields.erb . But that works perfectly and is located within quick_facts/_quick_fact_fields.html.haml
Update: My Controller Code
organizations_controller.rb
def update
if #organization.update_attributes(params[:organization])
..
elsif params[:organization][:quick_facts_attributes]
flash[:notice] = 'QuickFacts successfully updated.'
redirect_to organization_quick_facts_url(#organization)
else
flash[:notice] = 'Organization was successfully updated.'
redirect_to :action => 'edit'
end
else
# re-render last form
..
elsif params[:organization][:quick_facts_attributes]
render :template => "quick_facts/index"
else
render :action => 'edit'
end
end
end
It seems that you're trying to render a my_custom_field partial from one of the worker views found in app/views/worker, but apparently there's no such partial there. If you show us the code of the relevant views and controllers, it will be easier to pinpoint the exact problem.
On a side note, you could simply do validates_presence_of :name instead of defining a custom validation method to simplify your model. However, this is likely unrelated to the error you're describing and is just a general improvement suggestion.
Got it. I had two controllers.
quick_facts_controller.rb, and organizations_controller.rb
Once I deleted the update function in quick_facts_controller, it worked properly.

Resources