I'm using Mongoid/MongoDB with Rails and am trying to get a many-to-many relationship working. Basically books and categories where books can be in multiple categories. I keep getting an error:
undefined method `metadata' for "4e6aaec8ffb1900c19000002":String
when trying to add a new book and place it in categories. The following is what I'm using for the models, form, create method and what the server is reporting.
It looks like it is trying to update book_ids and the cat_ids, but it's not getting anything for the cat_ids. I've been trying lots of different things, but am not sure how to make this work.
The book model
class Book
include Mongoid::Document
field :title, :type => String
field :description, :type => String
has_and_belongs_to_many :cats
end
The cat model (categories)
class Cat
include Mongoid::Document
field :name, :type => String
field :description, :type => String
has_and_belongs_to_many :books
end
This is from the form that generates the category select and allows multiple selections:
<div class="field">
<%= label_tag "Cats" %><br />
<%= f.collection_select :cats, Cat.all, :id, :name, {}, :multiple => true %>
</div>
The create method in the books controller:
def create
#book = Book.new(params[:book])
redirect_to(#book, :notice => 'Book was successfully created.')
end
From the server when submitting the form:
Started POST "/books" for 127.0.0.1 at Fri Sep 09 17:30:37 -0700 2011
Processing by BooksController#create as HTML
Parameters: {"commit"=>"Create Book", "authenticity_token"=>"+OAIJM3NRPrUv0u1yfDEkkE2gvPQ7n0P6zPU9ZtqXlk=",
"utf8"=>"✓", "book"=>{"title"=>"The Golf & Tennis Book",
"cats"=>["4e6aaec8ffb1900c19000002", "4e6aaee8ffb1900c19000006"],
"description"=>"Both golf and tennis in this book, so it's in both categories."}}
MONGODB blog_development['system.namespaces'].find({})
MONGODB blog_development['cats'].update({:_id=>{"$in"=>[]}}, {"$pull"=>{"book_ids"=>BSON::ObjectId('4e6aafadffb1900c1900000b')}})
MONGODB blog_development['system.namespaces'].find({})
MONGODB blog_development['books'].update({"_id"=>BSON::ObjectId('4e6aafadffb1900c1900000b')}, {"$set"=>{"cat_ids"=>[]}})
Completed 500 Internal Server Error in 24ms
NoMethodError (undefined method `metadata' for "4e6aaec8ffb1900c19000002":String):
app/controllers/books_controller.rb:46:in `new'
app/controllers/books_controller.rb:46:in `create'
This is invalid
book.update_attributes({:cats => [ cat.id ]})
should be this
book.update_attributes({:cat_ids => [ cat.id ]})
or
book.update_attributes({:cats => [ cat ]})
Related
I am developing a web application with Rails in which I need to save two models with the same form. One of the models (Characteristic) belongs to the other (Facilities), so I decided to use a accepts_nested_attributes_for for the contained model. In the view, I use form_for to save the parent model (Characteristic) and another form_for for the contained model (Facilities). However, I always obtain the same error:
Started PUT "/facilities/537f8adfb4f2d7c124000056" for 127.0.0.1 at 2014-05-31 20:00:23 +0200
Processing by FacilitiesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"xr+cGlb9onx4o13IaS3K5UfYzmrb6pMdKljBc8byKdY=", "facilities"=>{"description"=>"Services", "characteristics"=>[{"id"=>"537f8adfb4f2d7c124000057", "title"=>"Room", "description"=>"Free"}]}, "commit"=>"Send", "id"=>"537f8adfb4f2d7c124000056"}
MOPED: 127.0.0.1:27017 COMMAND database=admin command={:ismaster=>1} (1.2872ms)
MOPED: 127.0.0.1:27017 QUERY database=hotel_abadi_development collection=facilities selector={"_id"=>"537f8adfb4f2d7c124000056"} flags=[:slave_ok] limit=0 skip=0 batch_size=nil fields=nil (0.4916ms)
MOPED: 127.0.0.1:27017 QUERY database=hotel_abadi_development collection=admins selector={"$query"=>{"_id"=>"537f8ad9b4f2d7c124000001"}, "$orderby"=>{:_id=>1}} flags=[:slave_ok] limit=-1 skip=0 batch_size=nil fields=nil (0.7987ms)
MOPED: 127.0.0.1:27017 QUERY database=hotel_abadi_development collection=admins selector={"$query"=>{"_id"=>"537f8ad9b4f2d7c124000001"}, "$orderby"=>{:_id=>1}} flags=[:slave_ok] limit=-1 skip=0 batch_size=nil fields=nil (0.7885ms)
MOPED: 127.0.0.1:27017 QUERY database=hotel_abadi_development collection=facilities selector={"$query"=>{"admin_id"=>"537f8ad9b4f2d7c124000001"}, "$orderby"=>{:_id=>1}} flags=[:slave_ok] limit=-1 skip=0 batch_size=nil fields=nil (0.8206ms)
Completed 500 Internal Server Error in 502.0ms
NoMethodError (undefined method `id' for #<ActiveSupport::HashWithIndifferentAccess:0xa6ae334>):
app/controllers/facilities_controller.rb:21:in `update'
Rendered /home/jesus/.rvm/gems/ruby-2.0.0-p353/gems/actionpack-3.2.15/lib/action_dispatch/middleware/templates/rescues/_trace.erb (2.5ms)
Rendered /home/jesus/.rvm/gems/ruby-2.0.0-p353/gems/actionpack-3.2.15/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (2.4ms)
Rendered /home/jesus/.rvm/gems/ruby-2.0.0-p353/gems/actionpack-3.2.15/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (17.7ms)
In order to solve the problem, I have also tried to use fields_for with the nested attributes but I obtain the same error. The main files which defines the application are the next:
app/models/facilities.rb
class Facilities
...
field :description, type: String
field :language, type: Symbol, default: :es
has_many :characteristics, dependent: :destroy
accepts_nested_attributes_for :characteristics, allow_destroy: true
...
end
app/models/characteristic.rb
class Characteristic
...
field :title, type: String
field :description, type: String
field :language, type: Symbol, default: :es
belongs_to :admin
has_one :upload, dependent: :destroy
accepts_nested_attributes_for :upload, allow_destroy: true
...
end
app/controllers/facilities_controller.rb
class FacilitiesController < ApplicationController
load_and_authorize_resource
respond_to :json, :html
...
def update
#facilities.update_attributes!( params[:facilities] )
respond_with #facilities, api_template: :general, location: hotel_path
end
...
end
app/views/facilities.html.haml
= form_for facilities, url: facilities_path( facilities ) do |f|
= f.text_area :description
.facilities_form
- facilities.characteristics.each_with_index do |char, index|
= form_for characteristic, url: characteristic_path( characteristic ), html: { method: :put } do |d|
= d.hidden_field :id, name: 'facilities[characteristics][][id]'
= d.text_field :title, width: 20, size: 20, name: 'facilities[characteristics][][title]'
= d.text_area :description, width: 20, rows: 4, cols: 22, name: 'facilities[characteristics][][description]'
= f.submit "Send"
Solved:
In the nested attributes, I manually put the name of the fields because I am using another form_for for them. In that names, I use "facilities[characteristics][][name_of_field]", but when we need to use nested attributes, we have to put "characteristics_attributes", so the correct name is "facilities[characteristics_attributes][][name_of_field]".
I've spotted that you use facilities instead of facility both in view and controller.
I assume the problem is load_and_authorize_resource in FacilitiesController try to load #facility extracting id from params. So, change facilities_path( facilities ) to facilities_path(#facility) or something similar.
1 - how can i get the id based on the first or last name selected?
2 - I was able to have it work for first name or last name, but i want to be able to have a full name (based on first or last name) when i start typing. For example when i type ev be able to see Evelin mars, Steve bach, Matt Evans, ....
I have a full name function in my person.rb model.
Is it possible?
view
<%= autocomplete_field_tag 'person_ids[]', '', autocomplete_person_last_name_segments_path, 'data-delimiter' => ',', :multiple => true, :id_element => '#some_element', :placeholder => "type a name" %>
controller
class SegmentsController < ApplicationController
autocomplete :person, :last_name
...
route.rb
resources :segments do
get :autocomplete_person_last_name, :on => :collection
end
person.rb
def full_name
"#{first_name} #{last_name}"
end
UPDATE
I were able to show the full name by adding :extra_data => [:first_name], :display_value => :full_name to the controller and it becomes
controller
class SegmentsController < ApplicationController
autocomplete :person, :last_name, :extra_data => [:first_name], :display_value => :full_name
...
But how do i get the id?
If you look at your AJAX response, you should see that by default, the id is also returned.
I believe that you actually want to use this id. This is what you can do in your view, using :ide_element => #Your_model_id" :
<%= autocomplete_field_tag 'some_label', '', your_autocomplete_path, :id_element => '#yourmodel_id', class: "form-control" %>
<%= f.hidden_field :yourmodel_id, :value => nil %>
You can have a look at the github page of the gem, there is a paragraph dealing with this.
I came back to the relatively "old book" Head First rails, which was published for Rails 2.3.
Now, going back again through those samples and using Rails 3 I came up with some questions.
Let's say that I'm adapting the sample for coconut airways and instead of flights and seats, I have a project and tasks.
The page shows a project description and below a list of tasks associated to that project. so far so good. now below that there is a form to create new task. This task needs a Task object and the project_id. here is when things do not work as before.
if you want to do it like the old style you will type:
<%= render :partial => "new_task",
:locals => {:task => Task.new(#project.id)} %>
well, this is showing the mass-assign error.
Then I tried to pass both as parameter:
<%= render :partial => "new_task",
:locals => {:task => Task.new, :project_id => #project.id} %>
and assign it in the partial
<%= f.hidden_field :project_id, :value => project_id %>
any hint?
EDITED:
class Task < ActiveRecord::Base
belongs_to :project
attr_accessible :title
end
class Project < ActiveRecord::Base
has_many :tasks
attr_accessible :description, :title
end
If you change your model's attr_accessible you can include these assignments to be made. For more information about attr_accessible and mass assignment see: Ruby on Rails API
I have the following models, which basically are trying to mean that a professor has knowledge of many subjects for a particular level. The subjects are fixed, so there will be no new subjects created, there will be just "related" to a professor through the knowledge join table.
class Subject < ActiveRecord::Base
# Self Associations
has_many :subcategories, :class_name => "Subject"
belongs_to :category, :class_name => "Subject",:foreign_key => "parent_id"
# Associations
has_many :knowledges
has_many :professors, :through => :knowledges
end
class Professor < ActiveRecord::Base
# Associations
has_many :knowledges
has_many :subjects, :through => :knowledges
...
end
class Knowledge < ActiveRecord::Base
# Associations
belongs_to :professor
belongs_to :subject
has_one :level
attr_accessible :subject_id, :professor_id
validates :subject_id, :uniqueness => { :scope => :professor_id }
end
I want to have a form that will let a professor to add a subject to his account, and I decided to have a form for a knowledge (as I want to be able to insert a level too).
It looks like this:
<%= simple_form_for #knowledge,:url => professor_knowledges_path, :html => { :class => 'form-horizontal' } do |f| %>
<div class="control-group select optional">
<%= label_tag "Subject Type", nil, :class => "select optional control-label"%>
<div class="controls">
<%= select_tag "Parent Subject", options_from_collection_for_select(#parent_subjects, "id", "name"), :id => "knowledge_parent_subject" %>
</div>
</div>
<%= f.input :subject_id, :collection => #subjects, :label => "Subject" %>
<%= f.input :level %>
<%= f.button :submit, t('add_form'),:class => 'btn-primary' %>
<% end %>
And in the create action of the Knowledges controller I have this:
def create
#knowledge = Knowledge.create(:professor_id => current_professor.id, :subject_id => params[:knowledge][:subject_id])
end
I would like/expect to get an ActiveRecord saying that this knowledge can't be inserted because there is a uniqueness violation, but nops, I just see a 500 in the logs and a rollback, but it seems the execution goes on. So my question is: What am I doing wrong, or how I could improve this modeling situation? I believe the form needs to be related to the join model as I want to have fields of that model on it...But maybe I am wrong, and I could do in an easy/cleaner way.
EDIT:
As asked in one of the comments, here is the log of the submission of the form and the 500 error right after the rollback:
Started POST "/professors/1/knowledges" for 127.0.0.1 at 2012-07-01 00:45:39 -0700
Processing by KnowledgesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"4JVyxWnIh37kyBwLwLGTHk/znsI1c5wrJvaWjKKT5tM=", "Parent Subject"=>"1", "knowledge"=>{"subject_id"=>"1"}, "commit"=>"Añadir", "professor_id"=>"1"}
Professor Load (0.4ms) SELECT `professors`.* FROM `professors` WHERE `professors`.`id` = 1 LIMIT 1
Completed 500 Internal Server Error in 4ms
I added some conditions in the create action, like this:
def create
#knowledge = Knowledge.new(:professor_id => current_professor.id, :subject_id => params[:knowledge][:subject_id])
if #knowledge.save
flash[:notice] = "Success..."
redirect_to professor_path(current_professor)
else
render :action => 'new'
end
end
And this actually shows the following right after the 500:
Completed 500 Internal Server Error in 6ms
ActiveRecord::RecordInvalid (Validation failed: Subject has already been taken):
I wonder why the exception is raised instead of just adding the errors into the object and let me manage that situation. Isn't what the following line should be doing?
validates :subject_id, :uniqueness => { :scope => :professor_id }
That error means you are trying to insert duplicate subject_id / professor_id pairs on that table. Most often happens when either the subject_id or professor_id is null.
Are you sure the controller is getting the correct parameters? I would check the logs to make sure the inserts are what you would expect.
I don't have enough reputation to comment...my answer is more some things to try than a definitive answer, sorry.
It looks like the save is failing due to validation errors. You can try to handle those in your 'else' block. The following will give you a description of all validation errors (useful for debugging).
#knowledge.errors.full_messages
You haven't shown what is happening in the 'new' action. I suspect this is where the errors are occurring.
Does the same issue occur (i.e. the validation problem) in the console? If so, try cleaning out your databases (beware - the following will erase & rebuild all your databases).
rake db:drop:all db:create:all db:migrate db:test:prepare
Also, if you haven't already, add an index to your migration for Knowledge to prevent duplicates being added to the db. e.g.
add_index :knowledges, [ :professor_id, :subject_id ], unique: true
I'm trying to build up on the following tutorial from railscast:
http://railscasts.com/episodes/196-nested-model-form-part-1
I'm trying to make everything work with mongodb and mongoid.
the scenario is:
I want to creates events linked to a location. Each events (dance class) contains many lessons.
So I thought that an embedded relationship would be perfect.
Here are my models
model Lesson
class Lesson
include Mongoid::Document
include Mongoid::Slug
field :name, :type => String
embedded_in :event
slug :name
end
model Event
class Event
include Mongoid::Document
include Mongoid::Slug
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
field :name, :type => String
field :description, :type => String
field :date, :type => DateTime
validates_presence_of :name
has_one :venue
referenced_in :venue
embeds_many :lessons
slug :name
end
model Venue
class Venue
include Mongoid::Document
include Mongoid::Slug
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
field :name, :type => String
field :location, :type => String
validates_presence_of :name, :location
belongs_to :event
slug :name
end
event controller
def create
#event = Event.new(params[:event])
if #event.save
flash[:notice] = 'Event was successfully created.'
end
respond_with(#Event, :location => events_url)
end
def update
# #event = Event.find(params[:id])
#event = Event.find_by_slug(params[:id])
if #event.update_attributes(params[:event])
flash[:notice] = "Event was succesfully updated"
end
respond_with(#event)
end
Then I have my Event view where I can create events and link it to a Venue. But I'd like to be abe to create the lessons from the Event view/model.
so I used the fields_for to generate a field linked to the Lessons model.
= form_for #event do |f|
.field
= f.label :name
%br/
= f.text_field :name
.field
= f.label :description
%br/
= f.text_area :description
.field
= f.label :venue_id
%br/
= f.collection_select :venue_id, Venue.all, :id, :name
.field
= f.label :date
%br/
= f.datetime_select :date
%h3 Add a Class
= f.fields_for :lessons do |builder|
= render "lesson_fields", :f => builder
.actions
= f.submit 'Save'
When I create or edit a new event I get an error message:
undefined method `extract_id' for "test":String
But the request parameter message on the error page shows my lessons value in the Event document.
"lessons"=>{"name"=>"test name lesson"}
When I remove the fields_for line, everything works fine. But then i don't know how to save the value for the nested documents.
I have same problem with embeds_many, but when i try change to has_many. It works!. Maybe you can try too.
can you post the exact code you use to create the Event, including parameters?
which version of Mongoid and Rails are you using?
First thing I noticed is that the following parameter hash does not match your Lessons model:
"lessons"=>{"content"=>"test name lesson"} # this looks wrong
this should be:
"lessons"=>{"name" => "test name lesson"}
Looks like your lessons form has the wrong label for the text input field .. it should be :name , not :content
To dry things up, you might want to try if the 'nested_form' gem works for you:
after installing the gem, use the nested_form_for instead of form_for in your view.
Check here for a more detailed description:
How can I handle this type of multi level forms in rails
See:
https://github.com/ryanb/nested_form (it's also referenced in the RailsCast you mentioned)
You also might want to check this:
field_for and nested form with mongoid
The conclusion of this story is...
I removed everything related to mongoid_slug and it started to work.
I then put everything back as it was to try to find out how to make it work with mongoid_slug and it just worked, like out of the box.
:(
Please include the following code in model event.rb
**accepts_nested_attributes_for :lessons**
This will fix your problem