Rails "follow" relationship controller issues - ruby-on-rails

I am working on an app where users' "projects" can follow "plant" objects from the database. I am getting the following error for the create action in my "Prelationships" controller (Plant Relationships that connect Users' projects to fixed "plant" objects) when I hit "Follow" for any number of plant objects in my app:
"You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]"
I realize this is a big question, and yes, I'm pretty much a newbie. All the migrations should be fine. I appreciate any help--even if it means suggesting a whole new way of tackling this issue.
Here's what my controller, called "Prelationships", looks like:
class PrelationshipsController < ApplicationController
def create
#plant = Plant.find(params[:prelationship][:pfollowed_id])
#project.follow!(#plant)
respond_to do |format|
format.html { redirect_to #project }
format.js
end
end
end
And my "Prelationships" model:
class Prelationship < ActiveRecord::Base
attr_accessible :pfollowed_id
belongs_to :pfollower, :class_name => "Project"
belongs_to :pfollowed, :class_name => "Plant"
validates :pfollower_id, :presence => true
validates :pfollowed_id, :presence => true
end
And my "Projects" model:
class Project < ActiveRecord::Base
attr_accessible :title, :address, :latitude, :longitude, :state
belongs_to :user
has_many :prelationships, :foreign_key => "pfollower_id",
:dependent => :destroy
has_many :pfollowing, :through => :prelationships, :source => :pfollowed
def pfollowing?(pfollowed)
prelationships.find_by_pfollowed_id(pfollowed)
end
def pfollow!(pfollowed)
prelationships.create!(:pfollowed_id => pfollowed.id)
end
end
And my "plant" model:
class Plant < ActiveRecord::Base
has_many :prelationships, :foreign_key => "pfollowed_id",
:class_name => "Prelationship"
has_many :pfollowers, :through => :reverse_prelationships,
:source => :pfollower
end
And, finally, my "_plants_form" partial for the view:
<%= form_for #project.prelationships.build(:pfollowed_id =>
#project_id) do |f| %>
<%= collection_select(:prelationships, :pfollowed_id, Plant.all, :id, :name,
options = {:prompt => "Select your plants"}, :class => "listselect") %>
<div class="actions"><%= f.submit "Pfollow" %></div>
<% end %>
Here's the error from my log:
Started POST "/prelationships" for 127.0.0.1 at 2011-11-20 23:31:57 +0100
Processing by PrelationshipsController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"NKqa1f0M2yPLQDHbRLnxl3SiwBeTus/1q1hpZjD7hgY=",
"prelationships"=>{"pfollowed_id"=>"5"}, "commit"=>"Pfollow"}
Completed 500 Internal Server Error in 14ms
NoMethodError (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]):
app/controllers/prelationships_controller.rb:4:in `create'
Rendered /Users/mmelone12/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-
3.0.9/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.4ms)
Rendered /Users/mmelone12/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-
3.0.9/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
(30.6ms)
Rendered /Users/mmelone12/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-
3.0.9/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within
rescues/layout (37.1ms)

Yep. Like Swanand point out, you should initialize #project object inside :create action. Like #project = Project.find(params[:project_id]) if you do not do it with a before_filter.
If you already instantiated #project before, see what happens when you manually try to retrieve #plant object in Rails console Plant.find(1)

Related

Rails 4 - Invite team mates to project

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.

Rails Unitialized Constant in controller

This is a follow up question to ActiveRecord having 2 FKs from the same column in a table I am getting the message 'uninitialized constant User::Message' and I don't know what is causing it.
My create action from my Message controller is
def create
#message = current_user.packages.build(message_params)
#message.sender = current_user
#message.receiver = User.find(params[:id])
#message.date = Time.now
if #message.save
flash[:success] = "Message Created!"
else
flash[:danger] = "Message Not Created!"
end
redirect_to user_path
end
I added the controller to my routes.rb file
resources :messages, only: [:create, :destroy]
My action from my User controller is
def show
#user = User.find(params[:id])
#message = #user.messages.build #error is thrown here
#package_feed_items = #user.packages.paginate(page: params[:page],:per_page => 5)
#route_feed_items = #user.routes.paginate(page: params[:page],:per_page => 5)
end
The form is in a partial in my 'messages' folder from the views directory. This is called from a view in my users controller with
<%= render 'messages/create_message_modal' %>
my Message model is now
class Message < ActiveRecord::Base
belongs_to :sender, class_name => :user, foreign_key => 'sender_id'
belongs_to :receiver, class_name => :user, foreign_key => 'receiver_id'
default_scope -> { order('created_at DESC') }
validates :receiver_id, presence: true
validates :sender_id, presence: true
validates :body, presence: true
validates :date, presence: true
state_machine :initial => :unOpened do
state :unOpened, value: "Un-Opened"
state :opened, value: "Opened"
state :deleted, value: "Deleted"
event :open do
transition :unOpened => :opened
end
event :delete do
transition :opened => :deleted
end
end
end
and my user model is
class User < ActiveRecord::Base
... other relations ...
has_many :messages
... validation & state machine ...
end
EDIT
The tail of my log file
Completed 500 Internal Server Error in 8ms
NameError (uninitialized constant User::Message):app/controllers/users_controller.rb:33:in `show'
Rendered /Users/jeff/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.1/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.4ms)
Rendered /Users/jeff/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.1/lib/action_dispatch/middleware/templates/rescues/_trace.erb (0.7ms)
Rendered /Users/jeff/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (0.8ms)
Rendered /Users/jeff/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.1/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (11.2ms)
Specify the class_name option to user model like below,
class User < ActiveRecord::Base
has_many :messages, class_name: 'Message'
end
Try this it should work, anyway you need to find why the direct relation is not working.

undefined method `[]=' for nil:NilClass

I keep getting this undefined error when I try to submit this form with nested attributes, not sure where it's coming from been wrestling with it for quite a while now, I am trying to let users select an option in the council model the submit their choice, I am not sure if I have my associations wired up correctly or if the error is coming from the form. Am a noob to rails. Thanks in advance.
Error Updated
Properties::BuildController#update
app/controllers/properties/build_controller.rb, line 21
Started PUT "/properties/5/build/council" for 127.0.0.1 at 2013-08-18 08:52:07 +0100
Processing by Properties::BuildController#update as HTML
Parameters: {"utf8"=>"✓","authenticity_token"=>"wBWQaxtBioqzGLkhUrstqS+cFD/xvEutXnJ0jWNtSa0=", "council_id"=>"1", "commit"=>"Save changes", "property_id"=>"5", "id"=>"council"}
Property Load (0.2ms) SELECT "properties".* FROM "properties" WHERE "properties"."id" = ? LIMIT 1 [["id", "5"]]
Completed 500 Internal Server Error in 35ms
NoMethodError - undefined method `[]=' for nil:NilClass:
Council View
<h1>Select Council</h1>
<%= form_tag url_for(:action => 'update', :controller => 'properties/build'), :method => 'put' do %>
<%= select_tag :council_id, options_from_collection_for_select(Council.all, :id, :name) %>
<%= submit_tag %>
<% end %>
Controller
class Properties::BuildController < ApplicationController
include Wicked::Wizard
steps :tenant, :meter, :council, :confirmed
def show
#property = Property.find(params[:property_id])
#tenants = #property.tenants.new(params[:tenant_id])
#meter = #property.build_meter
#council = #property.build_council
render_wizard
end
def edit
#property = Property.find(params[:property_id])
end
def update
#property = Property.find(params[:property_id])
params[:property][:status] = step.to_s
params[:property][:status] = 'active' if step == steps.last
#property.update_attributes(params[:property])
render_wizard #property
end
end
Council.rb
class Council < ActiveRecord::Base
attr_accessible :CouncilEmail, :name, :CouncilTel
belongs_to :property
end
UPDATED Propery.rb
class Property < ActiveRecord::Base
attr_accessible :name, :address_attributes, :tenants_attributes, :meter_attributes, :council_attributes, :property_id, :status
belongs_to :user
has_one :address, :as => :addressable
accepts_nested_attributes_for :address, :allow_destroy => true
has_one :council
accepts_nested_attributes_for :council, :allow_destroy => true
has_many :tenants, :inverse_of => :property
accepts_nested_attributes_for :tenants, :allow_destroy => true, :reject_if => :all_blank
has_one :meter
accepts_nested_attributes_for :meter, :allow_destroy => true
validates :name, :presence => :true
validates :address, :presence => :true
validates :tenants, :presence => true, :if => :active_or_tenants?
validates :council, :presence => true, :if => :active_or_council?
def active?
status == 'active'
end
def active_or_tenants?
(status || '').include?('tenants') || active?
end
def active_or_council?
(status || '').include?('council') || active?
end
end
I think this
params[:property]
is nil. So Ruby complains when doing
params[:property][:status] = 'foo'
You might want to do something like this:
if params[:property]
params[:property][:status] = 'foo'
end
However in your case the issue is because you are using a form_tag instead of a form_for, therefor params[:property] is not defined.
A better approach to check for nested attributes in ruby hashes nowadays is to use dig
Example:
params.dig(:property, :status)
If the key is not defined nil is returned.
If using a data file, make sure there's not a stray - in the data.yml. See this GitHub comment

rails fields_for parent id not being set on child model

I have been working from the rails api documents for NestedAttributes and FormHelper and searching stackoverflow.
I have the following code in my new.html.haml view:
=form_for listing, :html => {:id => :listing_form, :multipart => :true} do |f|
=f.fields_for :main_picture, (listing.main_picture || listing.build_main_picture) do |fmp|
=fmp.hidden_field :main, :value => 1
=fmp.file_field :image, :class => :picture_select
And the following code in my controller:
def create
#listing = Listing.new(params[:listing])
#listing.save ? redirect_to(:root) : render('listings/new')
end
Here is my listing.rb:
class Listing < ActiveRecord::Base
has_one :main_picture, :class_name => "Picture", :conditions => {:main => true}
attr_accessible :main_picture_attributes
accepts_nested_attributes_for :main_picture, :allow_destroy => true
end
And my picture.rb:
class Picture < ActiveRecord::Base
belongs_to :listing
validates_presence_of :listing
attr_accessible :listing, :main
end
And I get the following error message when I try and submit my form:
main_picture.listing: can't be blank
I can't work out why the framework is not automatically setting the listing_id field of the main_picture (object Picture) to id value of parent Listing object.
Is there something I am doing wrong?
Do you need the validates_presence_of :listing? I suspect that the child record is getting created before the parent object, and so it doesn't have an ID yet.
Removing that line and adding :dependent => :destroy to your has_one :main_picture would ensure you don't end up with orphan picture records.
Alternatively, rewrite your controller:
p = params[:listing]
#listing = Listing.new(p)
#picture = Picture.new(p.delete(:main_picture).merge({:listing => #listing})
etc.

Application design of controller (Rails 3)

This is hard to explain, but I will do my best:
I am building a system where user's can take courses. Courses are made up of steps, that must be taken in order. In the system there are 6 step types (Download, Presentation, Video, Text, Quiz, and Survey)
The way a user accesses a STEP currently is:
http://example.com/courses/2/course_steps/1
As you can tell course_steps are nested under courses.
Below is the show method in course steps:
def show
render "show_#{#course_step.step.step_type.name.downcase}"
end
As you can tell it basically picks a view to show_[TYPE] (quiz, survey, text..etc)
This works fine for simple steps such as a text, video, or download, but for complicated steps such as a quiz, this model does not work well for the following reasons:
How do I validate a form for a quiz or survey as I would be using a different controller (QuizAttemptsController).
It seems to break the REST principal as a quiz, survey..etc should be treated separately. (I know they are step types, but they can have their own actions and validations)
Step Model
class Step < ActiveRecord::Base
belongs_to :step_type
belongs_to :client
has_one :step_quiz, :dependent => :destroy
has_one :step_survey, :dependent => :destroy
has_one :step_text, :dependent => :destroy
has_one :step_download, :dependent => :destroy
has_one :step_video, :dependent => :destroy
has_one :step_presentation, :dependent => :destroy
has_many :course_steps, :dependent => :destroy
has_many :courses, :through => :course_steps
has_many :patient_course_steps, :dependent => :destroy
attr_accessible :step_type_id, :client_id, :title, :subtitle, :summary
validates :title, :presence=>true
validates :summary, :presence=>true
def getSpecificStepObject()
case self.step_type.name.downcase
when "text"
return StepText.find_by_step_id(self.id)
when "quiz"
return StepQuiz.find_by_step_id(self.id)
when "survey"
return StepSurvey.find_by_step_id(self.id)
when "download"
return StepDownload.find_by_step_id(self.id)
when "video"
return StepVideo.find_by_step_id(self.id)
when "presentation"
return StepPresentation.find_by_step_id(self.id)
end
end
end
Step Quiz Model:
class StepQuiz < ActiveRecord::Base
belongs_to :step, :dependent => :destroy
has_many :step_quiz_questions, :dependent => :destroy
has_many :quiz_attempts, :dependent => :destroy
accepts_nested_attributes_for :step
accepts_nested_attributes_for :step_quiz_questions, :allow_destroy => true
attr_accessible :step_id, :instructions, :step_attributes, :step_quiz_questions_attributes
validates :instructions, :presence=>true
end
CourseStep Model
class CourseStep < ActiveRecord::Base
belongs_to :step
belongs_to :course
validates_uniqueness_of :step_id, :scope => :course_id
def next_step()
Course.find(self.course.id).course_steps.order(:position).where("position >= ?", self.position).limit(1).offset(1).first
end
def previous_step()
Course.find(self.course.id).course_steps.order("position DESC").where("position <= ?", self.position).limit(1).offset(1).first
end
end
How would you suggest fixing this?
What you want to do is implement your Model as a Finite State Machine and continually reload the new or edit action until the desired state is reached, then your controller can display different views depending on state to allow multiple steps to happen.
One way I have solved the problem is by adding a member action of "submit_quiz"to the course_steps controller. I am not sure if I like this, as the code looks kind of ugly. I would appreciate feedback.(Note: I Am using CanCan so #course_step is created automagically in the course_steps_controller)
The things I don't like are:
show_quiz view has a lot of code in it
submit_quiz is in the course_steps_controller
quiz_attempt model has virtual attribute of quiz_questions (for validation purposes)
show_quiz.html.erb
<%= form_for (#quiz_attempt.blank? ? QuizAttempt.new(:started => Time.now.utc, :step_quiz_id => #course_step.step.step_quiz.id) : #quiz_attempt, :url => submit_quiz_course_course_step_path(#course_step.course, #course_step)) do |f| %>
<%= render :partial => 'shared/error_messages', :object => f.object %>
<% #course_step.step.step_quiz.step_quiz_questions.each do |quiz_question| %>
<h3><%= quiz_question.value %></h3>
<% quiz_question.step_quiz_question_choices.each do |quiz_question_choice| %>
<%= radio_button_tag("quiz_attempt[quiz_questions][#{quiz_question.id}]", quiz_question_choice.value, f.object.get_quiz_question_choice(quiz_question.id) == quiz_question_choice.value)%>
<%= quiz_question_choice.value %><br />
<% end %>
<% end %>
<%= f.hidden_field(:step_quiz_id)%>
<%= f.hidden_field(:started)%>
<%= submit_tag("Submit Quiz")%>
<% end %>
course_steps_controller.rb
def show
PatientCourseStep.viewed(current_user.id, params[:course_id], #course_step.step.id )
render "show_#{#course_step.step.step_type.name.downcase}"
end
def submit_quiz
#quiz_attempt = QuizAttempt.new(params[:quiz_attempt])
if !#quiz_attempt.save()
render 'show_quiz'
end
end
quiz_attempt.rb
class QuizAttempt < ActiveRecord::Base
belongs_to :step_quiz
belongs_to :patient
attr_accessor :quiz_questions
attr_accessible :step_quiz_id, :patient_id, :started, :ended, :correct, :incorrect, :quiz_questions
validate :answered_all_questions?
def get_quiz_question_choice(quiz_question_id)
unless self.quiz_questions.blank?
quiz_questions[quiz_question_id.to_s]
end
end
private
def answered_all_questions?
#Making sure they answered all the questions
if self.quiz_questions.blank? or self.quiz_questions.try(:keys).try(:count) != self.step_quiz.step_quiz_questions.count
errors.add_to_base "Not all questions were answered"
end
end
end
def submit_quiz
#quiz_attempt = QuizAttempt.new(params[:quiz_attempt])
if !#quiz_attempt.save()
render 'show_quiz'
end
end

Resources