I cleaned up my code, it looks much nicer now, but still doesn’t work. It starts to be a pain…
I just can’t save a parent with an existing child in nested form with parent has_many childs :through joinmodel.
In my case a Project has_many contributing Teachers and many contributing Pupils, both are Join-Models to Users. A Project has_many Schools as well.
(May be I should better name the models Teacherize and Pupilize or ProjectTeacher and ProjectPupil.)
As long as all records are new it all works fine. As soon as I want to connect an existing User as new Teacher of a new Project I get the following error:
Couldn't find User with ID=1 for Teacher with ID=
(1 is the correct user ID)
The problem should be somewhere her in my helper to setup empty form fields:
At least I guess so...
module ProjectsHelper
def setup_project(project)
if project.teachers.length <= 0 # usually there is just one teacher, so add one if there isn't one
teacher = project.teachers.build(:role_in_project => 'master')
if user_signed_in?
#teacher = project.teachers.new(:role_in_project => 'master', :user => current_user)
teacher.user = current_user # associate first teacher with current_user
else
#teacher = project.teachers.build
teacher.user = User.new # associate first teacher with a new user instance
end
end
if project.project_schools.length <= 0 # usually there is just one school, so add one if there isn't one
project_school = project.project_schools.build
project_school.school = School.new
end
if project.pupils.length < 3 # There can be up to 3 people, so add a blank fieldset as long as there are less than 3
pupil = project.pupils.build
pupil.user = User.new
end
project
end
end
These are my params received:
{"utf8"=>"✓", "authenticity_token"=>"uCCMk/s3SpDfR7+fXcsCOHPvfvivBQv8pVFVhdh6iro=",
"project"=>{
"teachers_attributes"=>{
"0"=>{
"id"=>"",
"user_attributes"=>{
"id"=>"1",
"gender"=>"male",
"title"=>"",
"firstname"=>"Firstname1",
"name"=>"Lastname1",
"faculty"=>"",
"fon"=>"",
"fax"=>""}
}
},
"id"=>"",
"title"=>"First Project",
"description"=>"This is a foo bar project!",
"presentation_type"=>"experimentell",
"note"=>""
},
"commit"=>"Register Project",
"action"=>"create",
"controller"=>"projects"
}
The case isn’t too abstract; it has to be possible to achieve it.
It’s just to connect a new parent record with an existing child record!
In this article, which is very good, exactly the case is explaint:
http://rubysource.com/complex-rails-forms-with-nested-attributes
# app/helpers/form_helper
module FormHelper
def setup_user(user)
user.address ||= Address.new
(Interest.all - user.interests).each do |interest|
user.interest_users.build(:interest => interest)
end
user.interest_users.sort_by! {|x| x.interest.name }
user/tmp/clean-controllers.md.html
end
end
There the interest is existing and gets connected through a new record in interest_uesers.
Why do I get the error when trying to do the same thing?
project.teachers.build(:user => current_user)
I studied several articles and casts, but none of them connect existing childs.
http://railscasts.com/episodes/196-nested-model-form-revised
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Rails 3.1+ Nested Forms Issue: Can't mass-assign protected attributes
Trying to use accepts_nested_attributes_for and has_and_belongs_to_many but the join table is not being populated
Quote: “accepts_nested_fields_for is used to create and modify related objects in a form. It can be used to populate join table, which is kind of what you're trying to do. However, using accepts_nested_fields_for to populate the join table is impossible with a HABTM relationship.”
That’s what I wanna do! Populate the join table!
It starts to be frustrating and I’d be glad to get some help!
My Models
class Project < ActiveRecord::Base
attr_accessible :title, :description, :presentation_type, :note,
:project_schools_attributes, :schools_attributes, :teachers_attributes, :pupils_attributes,
:users_attributes
validates_presence_of :title
validates_presence_of :description
validates_presence_of :presentation_type
has_many :project_schools, :dependent => :destroy
accepts_nested_attributes_for :project_schools
has_many :schools, :through => :project_schools
#accepts_nested_attributes_for :schools, :reject_if => :all_blank
has_many :pupils, :dependent => :destroy
accepts_nested_attributes_for :pupils, :reject_if => :all_blank
has_many :users, :through => :pupils
has_many :teachers, :dependent => :destroy
accepts_nested_attributes_for :teachers, :reject_if => :all_blank
has_many :users, :through => :teachers
#accepts_nested_attributes_for :users, :reject_if => :all_blank
end
class ProjectSchool < ActiveRecord::Base
attr_accessible :role_in_project, :comment,
:school_attributes, :school_id, :project_id
belongs_to :school
accepts_nested_attributes_for :school
belongs_to :project
end
class School < ActiveRecord::Base
attr_accessible :email, :fax, :fon, :name, :place, :street, :type_of_school, :www, :zip
has_many :project_schools
has_many :projects, :through => :project_schools
has_many :users # in real live they are named teachers and pupils but in this case the association goes directly to a user_id, not to teacher/pupil model
validates_presence_of :name, :type_of_school, :street, :place, :zip, :fon
validates :email, :format => { :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }, :allow_blank => true
end
class Teacher < ActiveRecord::Base
attr_accessible :role_in_project, :user, :user_attributes, :project_id, :user_id
belongs_to :project
belongs_to :user
accepts_nested_attributes_for :user
serialize :role_in_project
end
class Pupil < ActiveRecord::Base
attr_accessible :classname, :user_attributes #, :project_id, :user_id
belongs_to :project
belongs_to :user
accepts_nested_attributes_for :user
end
class User < ActiveRecord::Base
serialize :roles
belongs_to :school
has_many :teachers
has_many :pupils
has_many :projects, :through => :teachers
has_many :projects, :through => :pupils
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :gender, :firstname, :name, :street, :place, :title, :faculty, :assignment,
:classname, :zip, :fon, :fax, :school_id, :roles,
:password, :added_by_user_id, :password_confirmation, :remember_me
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable#, :validatable
after_initialize :init
def init
# the default guest user
self.roles ||= ['default'] #will set the default value only if it's nil
end
end
My controller
class ProjectsController < ApplicationController
require 'axlsx'
before_filter :load_page, only: [:show, :index, :destroy]
def new
#project = Project.new
end
def create
#project = Project.new(params[:project])
respond_to do |format|
if #project.save
sign_in(:user, #project.teachers[0].user) unless user_signed_in?
# TODO: send mail
# save as excel file in dropbox
save_in_dropbox(#project)
format.html { redirect_to #project, notice: t('project.was_created') }
else
logger.debug #project.errors.inspect
format.html { render action: "new" }
end
end
end
end
projects/_form.html.haml
%h1
= t('project.register_headline')
= simple_form_for( setup_project(#project), :html => {:class => 'form-horizontal'} )do |f|
= f.error_notification
#teachers-wrapper.well
%span.jumpanchor#lehrkraft_anchor
%fieldset.form-inputs
= f.simple_fields_for :teachers do |teacher|
= render "teacher_fields", :f => teacher
.school-wrapper.well
%span.jumpanchor#schule_anchor
%h2
Informationen zur Schule
%fieldset.form-inputs
= f.simple_fields_for :project_schools do |project_school|
= render "school_fields", :f => project_school
.project-wrapper.well
%span.jumpanchor#projekt_anchor
%h2
Informationen zum Projekt der Schüler
%fieldset.form-inputs
= f.hidden_field :id
= f.input :title, :input_html => { :class => 'span6' }
= f.input :description, :input_html => { :class => 'span6', rows: 5 }
= f.input :presentation_type, collection: ['theoretisch', 'experimentell'], as: :radio_buttons, :class => 'controls-row', :input_html => { :class => 'inline' }
.clearfix
= f.input :note, :input_html => { :class => 'span6', rows: 3 }
.pupils-wrapper.well
%span.jumpanchor#schuler_anchor
%fieldset.form-inputs
= f.simple_fields_for :pupils do |pupil|
= render "pupil_fields", :f => pupil
projects/_teacher_fields.html.haml
= f.simple_fields_for :user do |user|
=# render "teacher_user_fields", :f => user
%h2
Betreuende Lehrkraft
- if user_signed_in?
= user.input :email, :disabled => true, :input_html => {:class => 'email_validation'}
- else
= user.input :email, :autofocus => true, :input_html => {:class => 'email_validation'}, :hint => 'Dies muß Ihre eigene E-Mailadresse sein!'
.details
=# user.hidden_field :id
= user.input :id
= user.input :gender, collection: [:female, :male]
= user.input :title
= user.input :firstname
= user.input :name
= user.input :faculty
= user.input :fon
= user.input :fax
It is Rails 3.2 with Ruby 1.9.3
I'm using ActiveAdmin (0.4.0) with Rails (3.1.1).
I can't find a nice way/hack to handle multiple nested resources.
Considerer 3 models as:
class Program < ActiveRecord::Base
has_many :knowledges, :dependent => :destroy
end
class Knowledge < ActiveRecord::Base
belongs_to :program
has_many :steps, :dependent => :destroy
end
class Step < ActiveRecord::Base
belongs_to :knowledge
end
And the ActiveAdmin resources:
ActiveAdmin.register Program do
end
ActiveAdmin.register Knowledge do
belongs_to :program
end
ActiveAdmin.register Step do
belongs_to :knowledge
end
In routes.rb:
namespace :admin do
resources :programs do
resources :knowledges do
resources :steps
end
end
end
Here's the urls for the index of the programs, the knowledges and the steps :
http://localhost:3000/admin/programs
http://localhost:3000/admin/programs/1/knowledges
http://localhost:3000/admin/programs/1/knowledges/1/steps
No problem for the "Knowledge" admin but the "Step" admin don't keep the nested context.
For example, when I use filters in steps#index I'm redirected to:
http://localhost:3000/admin/knowledges/1/steps?params...
But it must have been:
http://localhost:3000/admin/programs/1/knowledges/1/steps?params...
Same problem when I create a new resource:
http://localhost:3000/admin/knowledges/1/steps/new
Instead of:
http://localhost:3000/admin/programs/1/knowledges/1/steps/new
Same problem with the breadcrumb... etc...
What I've tried so far in app/admin/steps.rb:
ActiveAdmin.register Step do
belongs_to :knowledge
config.clear_action_items!
action_item :only => :index do
link_to('Create Step', new_admin_program_knowledge_step_path(knowledge.program.id, knowledge.id))
end
index do
column :id
column :knowledge
column :title
column "Actions" do |step|
link_to("Voir", admin_program_knowledge_step_path(step.knowledge.program, step.knowledge, step), :class => "member_link show_link") +\
link_to("Editer", edit_admin_program_knowledge_step_path(step.knowledge.program, step.knowledge, step), :class => "edit_knowledge member_link edit_link", :id => "knowledge_#{dom_id(knowledge)}") +\
link_to("Supprimer", admin_program_knowledge_step_path(step.knowledge.program, step.knowledge, step), :class => "member_link delete_link", :method => :delete, :confirm => "Delete?")
end
end
filter :id
filter :title
filter :subtitle
filter :stage_type
filter :order_by
filter :created_at
filter :updated_at
form :partial => "form"
end
And in app/views/admin/steps/_form.html.erb I must use the activeadmin formbuilder:
<%= semantic_form_for(resource, :url => admin_program_knowledge_steps_path(resource.knowledge.program, resource.knowledge), :builder => ActiveAdmin::FormBuilder) do |f|
f.inputs "Step" do
f.input :knowledge, :as => :hidden
f.form_buffers.last << f.template.content_tag(:li, f.template.content_tag(:label, "Knowledge")+f.template.content_tag(:p, f.object.knowledge.title))
f.input :title
f.input :order_by
end
f.buttons
end %>
Well... I'm stuck.
How to handle this nicely? Any clues appreciated...
Well, the solution is pretty simple...
https://github.com/josevalim/inherited_resources
ActiveAdmin.register Step do
controller do
nested_belongs_to :program, :knowledge
end
end
I'm working on a ActiveAdmin app with this models :
User
class User < ActiveRecord::Base
# A User has many roles for interact on a project
has_many :roles, :dependent => :destroy
has_many :projects, :through => :role
end
Role
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Project
class Project < ActiveRecord::Base
# A project has many roles for interact
has_many :roles, :dependent => :destroy
has_many :users, :through => :role
accepts_nested_attributes_for :roles
end
To add users with a role on each project I make this form :
form do |f|
f.inputs "Details" do # Project's fields
f.input :title
f.input :code
end
f.has_many :roles do |app_f|
app_f.inputs do
if !app_f.object.nil?
app_f.input :_destroy, :as => :boolean, :label => "Destroy?"
end
app_f.input :user
app_f.input :senior_author
end
end
f.buttons
end
My first question is how can I make a with user.firstname + user.lastname. Actually I have something like this :
#<User:0x007fb98a7d6568>
Second question is my Role model is a list of boolean attributes :
:senior_author
:first_author
:viewer
....
Can I make a with that ?
Another solution would be to just define to_s in the model:
def to_s
"#{email} | #{firstname} #{lastname}"
end
No need to set :label_method.
Just add :label_method => lambda:
app_f.input :user, :label_method => lambda{|u| "#{u.email} | #{u.firstname} #{u.lastname}" }
I fix it by adding this method to models/user.rb
# format label for formtastic dropdown menu
def to_label
"#{email} | #{firstname} #{lastname}"
end
And I use it like this :
app_f.input :user, :include_blank => false, :label_method => :to_label
I'm currently making association like this :
show do
h3 project.title
panel "Utilisateurs" do
table_for project.roles do
column "Prenom" do |role|
role.user.firstname
end
column "Nom" do |role|
role.user.lastname
end
column "email" do |role|
role.user.email
end
column "Role" do |role|
role.role_name.name
end
end
end
end
# override default form
form do |f|
f.inputs "Details" do # Project's fields
f.input :title
f.input :code
end
f.has_many :roles do |app_f|
app_f.inputs do
# if object has id we can destroy it
if app_f.object.id
app_f.input :_destroy, :as => :boolean, :label => "Supprimer l'utilisateur du projet"
end
app_f.input :user, :include_blank => false, :label_method => :to_label
app_f.input :role_name, :include_blank => false
end
end
f.buttons
end
I have the following associations :
Project
has_many :roles, :dependent => :destroy
has_many :users, :through => :role
User
has_many :roles, :dependent => :destroy
has_many :projects, :through => :role
Role
belongs_to :user
belongs_to :project
belongs_to :role_name
RoleName
has_many :roles
When I try to destroy user association through my form nothing happen, any idea to solve this ?
Or to add delete link to my show block ?
Try to add accepts_nested_attributes_for to your Project model (and roles_attributes to attr_accessible):
class Project < ActiveRecord::Base
has_many :roles, :dependent => :destroy
has_many :users, :through => :role
accepts_nested_attributes_for :roles, :allow_destroy => true
attr_accessible :roles_attributes, (+ all you had here before)
...
end
allow_destroy: true is the root of this issue.
when I visit 'company/new', then I get the following error:
undefined method `companies_path'
Extracted source (around line #1):
1: <%= form_for(#company) do |f| %>
But when I visit 'company/1/edit' (which uses the same form) everything works fine.
This is the company controller for 'new' and 'edit':
def new
#company = Company.new
end
def edit
#company = Company.find(params[:id])
end
And this is (part of) the form:
<%= form_for(#company) do |f| %>
<!-- Show errors -->
<%= render('layouts/form_errors', :object => #company) %>
I really don't understand the error message, because 'companies_path' is not being used in the code?
Update: here is the routes.rb:
get "users_dashboard/show"
get "login" => "sessions#new", :as => "login"
get "logout" => "sessions#destroy", :as => "logout"
resources :company
resources :relations
resources :activities
resources :contacts
resources :notes
resources :tasks
resources :users
resources :sessions
get "site/index"
get "site/features"
get "site/dashboard"
root :to => 'users_dashboard#show'
And here is the company model:
class Company < ActiveRecord::Base
has_many :users
has_many :relations
has_many :contacts, :through => :relations
has_many :notes, :through => :contacts
has_many :tasks, :through => :contacts
has_one :subscription
accepts_nested_attributes_for :subscription
attr_accessible :name, :address1, :address2, :zipcode, :city, :country, :email, :website, :telephone, :twitter, :linkedin, :code
validates :name, :address1, :zipcode, :city, :country, :code, presence: true
validates_length_of :code, :maximum => 3
end
You should change
resources :company
to
resources :companies