ActiveModel::MissingAttributeError - can't write unknown attribute Rails 4 - ruby-on-rails

So I am still quite new to the rails framework.
I am having problems when build a resource.
I am using an AJAX query to POST a JSON array of project_materials to a cart with a line_items association
(I am following the Agile Web Development Book).
This is the error I get
ActiveModel::MissingAttributeError - can't write unknown attribute project_materials_id':
This is what my controller looks like.
in line_items controller
# POST /line_items
# POST /line_items.json
def create
#materialsArray = params[:materials]
project_id = params[:project_id]
#cart = current_cart
#pm = ProjectMaterials.find(1)
#lm = #cart.line_items.build(:project_materials => pm)
#lm.save
array = JSON::parse(materialsArray)
#puts array
array.each do |key|
pm = ProjectMaterials.find_by_svg_id(key['id'])
lm = #cart.line_items.create!(:project_materials => pm)
lm.save
end
render :js => "window.location = '#{project_path(Project.find(project_id))}'"
end

It looks like you need to examine your #cart object and make sure that you have set up accepts_nested_attributes_for line_items in that object.

I made a big mistake. I labelled "project_materials_id" as "project_material_id" in the database.

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

Rails saving arrays to separate rows in the DB

Could someone take a look at my code and let me know if there is a better way to do this, or even correct where I'm going wrong please? I am trying to create a new row for each venue and variant.
Example:
venue_ids => ["1","2"], variant_ids=>["10"]
So, I would want to add in a row which has a venue_id of 1, with variant_id of 10. And a venue_id of 2, with variant_id of 10
I got this working, and it's now passing in my two arrays. I think I am almost there I'm not sure the .each is the right way to do it, but I think that I'm on the right track haha. I have it submitting, however, where would I put my #back_bar.save? because this might cause issues as it won't redirect
Thanks in advance.
def create
#back_bar = BackBar.new
#venues = params[:venue_ids]
#productid = params[:product_id]
#variants = params[:variant_ids]
# For each venue we have in the array, grab the ID.
#venues.each do |v|
#back_bar.venue_id = v
# Then for each variant we associate the variant ID with that venue.
#variants.each do |pv|
#back_bar.product_variant_id = pv
# Add in our product_id
#back_bar.product_id = #productid
# Save the venue and variant to the DB.
if #back_bar.save
flash[:success] = "#{#back_bar.product.name} has been added to #{#back_bar.venue.name}'s back bar."
# Redirect to the back bar page
redirect_to back_bars_path
else
flash[:alert] = "A selected variant for #{#back_bar.product.name} is already in #{#back_bar.venue.name}'s back bar."
# Redirect to the product page
redirect_to discoveries_product_path(#back_bar.product_id)
end
end # Variants end
end # Venues end
end
private
def back_bar_params
params.require(:back_bar).permit(:venue_id,
:product_id,
:product_variant_id)
end
as i said in comments
this is untested code and just showing you how it's possible to do with ease.
class BackBar
def self.add_set(vanue_ids, variant_ids)
values = vanue_ids.map{|ven|
variant_ids.map{|var|
"(#{ven},#{var})"
}
}.flatten.join(",")
ActiveRecord::Base.connection.execute("INSERT INTO back_bars VALUES #{values}")
end
end
def create
# use in controller
BackBar.add_set(params[:venue_ids], params[:variant_ids])
# ...
end

Call 'structure' to save in postgres using rails

I have a 'publication' data structure, and my table name in postgres is 'publications'. I get a tweet from streaming and parse it according in class TweetFetcher. How do I call publication controller to save it to the database? PublicationController has the standard scrum abilities (new, create, show, edit...), where create is:
def create
#publication = Publications.new(params[:publication])
if #publication.save
redirect_to :action => 'list'
else
#subjects = Subject.find(:all)
render :action => 'new'
end
end
and parte of my twitter code is
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
#save here. like this?
#PublicationController.create(parsedTweet)
end
end
You shouldn't be using your controller in this situation, instead you can just build your record like this:
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
publication = Publications.new
publication.sample_field = pT.sample_field
... # set additional attributes
publication.save
end
end
Additionally, here is some advice/information that is not directly related to your question, but are things you should know:
The class name of your models should be singular, so instead of Publications, it should be Publication.
Method and variable names should be in snake case rather than camel case (i.e. save_tweet instead of saveTweet or parsed_tweet instead of parsedTweet).
The new hash syntax is prefered, unless of course you are using a Ruby version below 1.9. This looks like { key: value } instead of { key => value }.
Indentation for Ruby code is typically 2 spaces.
That being said, I would change your code like this:
PublicationsController#create
def create
#publication = Publication.new(params[:publication])
if #publication.save
redirect_to action: 'list'
else
#subjects = Subject.find(:all)
render action: 'new'
end
end
TweeFetcher
class TweetFetcher
def save_tweet(tweet)
parsed_tweet = JSON.parse(tweet)
publication = Publication.new
publication.sample_field = parsed_tweet.sample_field
... # set additional attributes
publication.save
end
end

how to give condition to loop through a variable containing data from different models

I have following four variables in my controller index action which are retrieving data from different models and i have joined them as follows:
#forum = Forum.where(:user_id => #users.collect(&:user_id)).all
#poll=Poll.where(:created_by => #users.collect(&:user_id)).all
#article = Article.where(:user_id => #users.collect(&:user_id)).all
#jobpost = Jobplacement.where(:user_id => #users.collect(&:user_id)).all
#post = #article + #jobpost + #forum + #poll
In the view i wanted to loop through #post so i wrote - #post.reverse.each do | post| but the problem is this post contains data from 4 different models and forum ,article,jobpost is having :user_id column whereas poll is having created_by as the column name for user_id field.because of this m getting the error undefined method `user_id' for # in following lines
- if User.find(post.user_id).basic_info or User.find(post.created_by).basic_info
- if User.find(post.user_id).basic_info.profilephoto?
= image_tag User.find(post.user_id).basic_info.profilephoto.url(:thumb)
how can i give condition like
- if User.find(post.user_id).basic_info or User.find(post.created_by).basic_info
or
- if User.find(post.user_id or post.created_by).basic_info
this seems like a messy thing to be doing.
but something like this should work:
user_id = post.user_id || post.created_by
if User.find(user_id).basic_info
BUT, you really shouldn't be doing database calls from a loop in your view.
It would be cleaner to do all the data collection in the controller and models.

Rails 3 - controller Conditional?

I have a controller that is:
def create
.
.
#project = Project.find(params[:project]
#Log = Logs.create(params[:action]).merge(:project_id => #project.id))
...
end
The issue hereis that sometimes when DEF CREATE, I'll have a project and I want to record that. Other times I won't and that's perfectly fine, I still want to create the #Log
What's the right way in Rails to handle this. I'll want to make sure:
The first line #project doesn't error.
Also that the #log doesn't error but inserts '' or NIL whatever is rails standard.
Thank you
Try this:
def create
#project = (project_id = params[:project_id]).blank? ? nil :
Project.find(project_id)
#Log = Logs.create(params[:action].merge(#project.nil? ? {} :
{:project_id => #project.id}))
end
If the input has a project_id, then above solution will throw an error if a project with the given id is not found. If you don't want this behavior use find_by_id instead of find.
Create a protected method near the bottom of your controller like so:
protected
def project_id
# return the cached value if we've already figured it out
return #project_id if defined?(#project_id)
# get the project by id in a failsafe way
project = params[:project_id] ? Project.find_by_id(params[:project_id]) : nil
# return nil if the project is nil, so we don't try to call "id" on it
return #project_id = nil if project.nil?
# cache and return the project id
#project_id = project.id
end
Notice I changed the parameter to :project_id instead of just project. This fits the rails convention better. Now in your create action, and all other actions, you can call it safely:
#Log = Logs.create(params[:action]).merge(:project_id => project_id))
I hope this helps!

Resources