I have three models, users, reports, and receipts. Users have many reports, and reports have many receipts.
Now, I have a form set up to create, or edit reports. And I need to nest another form to create and edit receipts. I followed the rails guide (section - building a multi modeled form) and edited my models, and have added the build line into my form view but Im getting that 'uninitialized constant' error.
Here are my models:
class Report < ActiveRecord::Base
belongs_to :user
has_many :receipts
attr_accessible :cash_advance, :company, :description, :end_date, :mileage, :report_name,
:start_date, :receipts_attributes
validates_presence_of :company, :description, :end_date, :report_name#, :start_date
validates_uniqueness_of :report_name
accepts_nested_attributes_for :receipts, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
end
class Receipts < ActiveRecord::Base
belongs_to :report
attr_accessible :account_code, :amount, :company_card, :date, :description, :lobbying_expense, :vendor
end
and my form:
<%# #report.receipts.build %>
<%= form_for([current_user,#report]) do |f| %>
...
<%= f.fields_for ([#report, #report.receipts.build ]) do |receipt| %>
...
<% end %>
<% end %>
my routes (which Im not sure if I should have edited, but I got the same error before I added the receipts resources)
resources :users do
resources :reports do
resources :receipts
end
end
I didnt edit the reports controller since the rails guide didnt show any mention of it, its its only:
def new
#report = current_user.reports.new
end
def edit
#report = current_user.reports.find(params[:id])
end
What am I doing wrong?
edit - I changed my form for the receipts so the form_for takes in [#report, #report.receipts.build] but now I get the error:
uninitialized constant Report::Receipt
How do I get this form to work?
UGH! I messed up when I generated the model and gave it a plural name instead of a singular name. This guy, right here, is a fool.
Related
Im trying to add functionality to my Rails 4 app which allows a user (who creates a project) to invite others to join their project team.
I found this tutorial, which I've found helpful: https://coderwall.com/p/rqjjca/creating-a-scoped-invitation-system-for-rails
To this point, I have the following set up:
User
has_one :profile, dependent: :destroy
Profile
belongs_to :user
has_many :teams, foreign_key: "team_mate_id"
has_many :team_projects, through: :teams, source: :project
has_many :invitations, :class_name => "Invite", :foreign_key => 'recipient_id'
has_many :sent_invites, :class_name => "Invite", :foreign_key => 'sender_id'
Project
belongs_to :profile
has_one :team
has_many :team_mates, through: :team
has_many :invites
Invite
belongs_to :project
belongs_to :sender, :class_name => 'Profile'
belongs_to :recipient, :class_name => 'Profile'
Team
belongs_to :project
belongs_to :team_mate, class_name: "Profile"
In my form, I have:
<%= simple_form_for(#invite, :url => invites_path) do |f| %>
<%= f.hidden_field :project_id, :value => #invite.project_id %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.input :expiry, :as => :date_picker, :label => "When do you need a response to this invitation?" %>
<%= f.submit 'Send' %>
<% end %>
Then in my show (rendered on the projects show) I have:
<%= render :partial => 'projects/invite_team_mate' %>
In my invites controller, I have:
class InvitesController < ApplicationController
def new
#invite = Invite.new
end
def create
#invite = Invite.new(invite_params)
#invite.sender_id = current_user.profile.id
if #invite.save
#if the user already exists
if #invite.recipient != nil
#send existing user email invitation to join project team
InviteMailer.existing_user_invite(#invite).deliver
#Add the user to the user group - inivte rsvp pending
#invite.recipient.project.push(#invite.project)
else
#send new user email invitation to join as a user and this project team
#invite.recipient.project.push(#invite.project)
# InviteMailer.new_user_invite(#invite, new_user_registration_path(:invite_token => #invite.token)).deliver
end
else
# oh no, creating an new invitation failed
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invite
#invite = Invite.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def invite_params
params[:invite].permit(:email)
end
end
I can't figure out what else needs to happen to make this work.
When I save all this and try to invite an email address, I get this error:
undefined method `project' for nil:NilClass
That happens despite the form I use to send the invite being shown on the projects show page.
You need to add project_id and recipient_id to your invite_params, and add the recipient to your form (as a text_field, or hidden field, depending on your use case):
# controller
def invite_params
params[:invite].permit(:email, :project_id, :recipient_id)
end
# form
<%= simple_form_for(#invite, :url => invites_path) do |f| %>
...
<%= f.hidden_field :recipient_id, :value => get_recipient_id %>
...
<% end %>
Error is due to #invite.project_id, because #invite has no data so it's throwing error
<%= f.hidden_field :project_id, :value => #invite.project_id %>
replace this with or with some other desired logic
select_tag "people", options_from_collection_for_select(#projects, "id", "name")
In controlller
def new
#invite = Invite.new
#projects = current_user.team_projects // here you have to add your logic, for which project you want to invite or let me know
end
I'm finding following code very strange
if #invite.recipient != nil
...
#Add the user to the user group - inivte rsvp pending
#invite.recipient.project.push(#invite.project)
else
#send new user email invitation to join coalfacer and this project team
#invite.recipient.project.push(#invite.project)
...
end
How is that you call the same code #invite.recipient. even if #invite.recipient is Nil?!
By the way, ensure you understand why this code is written for in the controller, what it means
def invite_params
params[:invite].permit(:email)
end
For your convenience, refrain from coping code you don't understand. Also, even if you do so, do that in small portions and try after each one, so you can localize the error if any. Working in small increments is essential. You can't do a horde of changes and then just ask "what's wrong about his plenty of code".
Finally, I suggest you write specific questions, using MCVE principle. You have to extract specific portions of your code relevant to the issue, and be specific on the problem. If you put whole bunch of code, including irrelevant one, it's much much harder to help.
I am having 2 problems.
1) formtastic will only show the last input field instead of all of them. In this case it will only display:
r.input :sort_order
2) I had to do some wierd wrap f.inputs around each field to get it to show up which i believe is the wrong way. But when i submit the form it says Unpermitted parameters: page. When I did clearly define page I dont know how else to get permit params accept this.
Here is my model
class Fact < ActiveRecord::Base
has_one :page, as: :pageable, dependent: :destroy
accepts_nested_attributes_for :page
end
The other model:
class Page < ActiveRecord::Base
belongs_to :pageable, polymorphic: true
end
My active admin:
ActiveAdmin.register Fact do
permit_params :id, page_attributes: [:type, :name, :description :sort_order ]
form do |f|
f.inputs "My Page", for: [:page, f.object.page || Page.new] do |r|
r.input :name
r.input :description
r.input :sort_order
end
end
end
I can't get ActiveAdmin to save the associated model when the initial model is saved.
I have two models that look like this:
# app/models/account.rb
class Account < ActiveRecord::Base
has_one :endpoint, inverse_of :account, class_name: 'Abcd::Endpoint'
accepts_nested_attributes_for :endpoint
delegate :access_key, to: :endpoint
end
# app/models/abcd/endpoint.rb
class Abcd::Endpoint < ActiveRecord::Base
attr_accessible :account_id, :access_key
belongs_to :account
end
My ActiveAdmin file looks like:
# app/admin/account.rb
Activeadmin.register Account do
form do |f|
f.inputs do
f.input :name
end
f.inputs title: 'endpoints', for: [:endpoint. f.object.endpoint || Endpoint.new] do |nested_form|
nested_form.input :access_key,
label: 'Access Key',
as: :string
end
f.actions
end
show do |account|
row 'endpoint has access_key' do
account.access_key
end
end
end
When I click on "Update Account" the Account gets updated but the Endpoint
model doesn't get updated. It appears that the Endpoint attributes aren't
being sent to the Endpoint model.
Does anyone know how to get the Endpoint model to get updated with its
attributes or what I need to fix?
give it a try
form do |f|
f.inputs "Account" do
f.input :name
end
f.inputs do
f.has_one :endpoint, inverse_of :account, class_name: 'Abcd::Endpoint' do |nested_form|
nested_form.input :access_key,
nested_form.label('Access key')
end
end
f.actions
end
I've been puzzling over this for quite some time now and can't figure it out.
I've got 2 models:
class Vehicle < ActiveRecord::Base
attr_accessible :year, :capacity,
:size, :body, :model_id, :maker_id, :parameters_attributes
validates :year, numericality: { greater_than: 1900 }
validates :year, :capacity, :size, :body, presence: true
belongs_to :model
belongs_to :maker
has_many :parameters
accepts_nested_attributes_for :parameters
end
and
class Parameter < ActiveRecord::Base
attr_accessible :tag, :value
validates :tag, :value, presence: true
belongs_to :vehicle
end
in new vehicle view i've got:
= form_for [:admin, #vehicle], html: { multipart: true } do |f|
=# some other stuff in between
= f.text_field :value, size: 4
I get this error
undefined method `value'
Just can't seem to get it working. Help, anyone?
EDIT
routes.rb
resources :vehicles
resources :parameters
resources :makers do
resources :models
end
If you are using nested form, you should have something like
f.fields_for :parameters do |parameter|
and than:
parameter.text_field :value, size: 4
Also, remember to create the some parameters in the controller, for example:
def new
#vehicle = Vehicle.new
2.times { #vehicle.parameters.build } #it will create 2 parameters
...
end
f refers to #vehicle, it seems only Parameter bears this field. That's why it fails.
Sidenotes:
In Vehicle you have accepts_nested_attributes_for :parameters but you don't have parameters_attributes in the attr_accessible, can't be good.
If you want to call the relationship in the form consider using fields_for
Ok, I've made a mess of things.
Firstly I've been trying to
def new
#vehicle = #vehicle.parameters.build
end
hence the error undefined method. After a while I got to the correct syntax, which is the one gabrielhilal added after a while.
def new
#vehicle = Vehicle.new
#vehicle.parameters.build
end
No matter ;) Still had problems, because after clicking "create" he wouldn't add records in the database. Turned out that I've set the validates presence: true for tag, but didn't assign any value to it. After fixing that, it worked like a charm. Thanks a lot for all the help.
On to the next puzzle.
I have a projects resource that has many tasks. I want to ensure that every task has a project_id by adding validates_presence_of :project_id to the tasks model.
However, when creating a new project with tasks, the project_id won't be available until the record saves, therefore I can't use validates_presence_of :project_id.
So my question is, how do I validate presence of project_id in the task model? I want to ensure every task has a parent.
...
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
accepts_nested_attributes_for :tasks, :allow_destroy => true
...
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project_id
Your code works:
If you validates_presence_of :project, then as long as the project is there, it will validate. But if your project is unsaved, you could still save the task.
If you validates_presence_of :project_id, then the integer must be there, indicating a saved value.
Here's rSpec that proves the point. If you validate :project_id, you can't save a task without saving the Project.
class Task < ActiveRecord::Base
belongs_to :project
end
/specs/model_specs/task_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'
describe Task do
before(:each) do
#project = Project.new
end
it "should require a project_id, not just a project object" do
task = Task.new
task.project = #project
Task.instance_eval("validates_presence_of :project_id")
task.valid?.should == false
end
it "should not be valid without a project" do
task = Task.new
task.project = #project
Task.instance_eval("validates_presence_of :project")
task.valid?.should == false
task.save.should == false
end
end
See here for the definitive answer :
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy, :inverse_of => :project
accepts_nested_attributes_for :tasks, :allow_destroy => true
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project
Not so elegant if you ask me... It should transparently validate.
Maybe I don't understand something, but it looks like you are trying to cheat rails. Why don't you just do like this:
class Task < ActiveRecord::Base
belongs_to :project
validate_presence_of :project
end
Take a look at this:
https://rails.lighthouseapp.com/projects/8994/tickets/2815-nested-models-build-should-directly-assign-the-parent
One thing I have done in the past is add: validates_presence_of :parent_id, :on => :update. Not great but it helps tighten the net a little.
I think you're having the same issue I dealt with. I have two models, Account and User, and when the account is created the first user is created through a #account.users.build. The User model has a validates_presence_of :account validation.
To make the first user pass validation, I added the following code to my Account model:
before_validation_on_create :initialize_users
def initialize_users
users.each { |u| u.account = self }
end
In reality you need both:
validates_presence_of project
validates_presence_of project_id
That way the task will not be saved in either of the following cases assuming that you have only 2 valid projects in the database, i.e. project id 99 is invalid:
task.project_id = 99
task.save
task.project = Project.new
task.save
I hope this is of help to someone.
Your Project class must define
accepts_nested_attributes_for :tasks
See Nested Model Form on Railscasts for more details on how to make the form.
EDIT:
In your form you should have something like this:
_form.html.erb
<% form_for #project do |f| %>
# project fields...
<% f.fields_for :tasks do |builder| %>
<%= render 'task_fields', :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add task", f, :tasks %></p>
<%= f.submit %>
<% end %>
_task_fields.html.erb
<%= f.label :name, "Task name:" %>
<%= f.text_field :name %>
# task fields...
<%= link_to_remove_fields "Delete task", f, :tasks %>
link_to_add_fields and link_to_remove_fields are methods defined in application_helper to add/delete fields dynamically.