undefined method `email' for nil:NilClass error - ruby-on-rails

I am trying to send an email notification once a nested attribute form has been updated, but I keep bumping into this error, I think it because I am sending the email notification before council model has updated the object in the database thus am getting a nil for Property.councils.email, any help would be greatly appreciated.
Property Mailer
class PropertyMailer < ActionMailer::Base
default from: "myemail#gmail.com"
def welcome_email(property, tenants, councils)
#property = property
#tenants = tenants
#councils = property.councils
mail(:to => property.councils.first.email, subject: 'Here are your property details')
end
end
property.rb
class Property < ActiveRecord::Base
has_many :council_histories
accepts_nested_attributes_for :council_histories, :reject_if => :send_email
has_many :councils, through: :council_histories, :foreign_key => :council_id
accepts_nested_attributes_for :councils
def send_email
if council_histories.where(council_id_changed?)
PropertyMailer.welcome_email(self, tenants, councils).deliver
end
end
end
Update #
'Property/Build Controller' Nested controller, am using wicked wizard form gem, to build a multi-step form.
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
#property.council_histories.build do |council_history|
#council = council_history.build_council
end
render_wizard
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
Form View
<%= simple_form_for #property, url: wizard_path, :method => 'put' do |f| %>
<%= f.simple_fields_for :council_histories do |builder| %>
<%= builder.input :property_id, as: :hidden, input_html: { value: #property.id } %>
<%= builder.input :council_id, :collection => Council.all %>
<%= builder.submit %>
<% end %>
<% end %>

As Marek Lipka said, the property.councils most likely returns an empty hash, however that's only part of the story. I believe the issue is in your Property model, with this line:
has_many :council_histories
accepts_nested_attributes_for :council_histories, :reject_if => :send_email
^^
This is the problem here
Your original hypothesis is correct, I believe are you attempting to send the email before the :councils relationship has a chance to populate. The :reject_if method is used to, as the name implies, throw out the data in certain circumstances (I never use it myself so can't think of any good examples, but I'm sure there are plenty). Check here for more info.
Do you absolutely need the email to be sent BEFORE the object is persisted? If not, maybe another alternative would to use one of the ActiveRecord::Callback methods, such as after_commit, like so:
class Property < ActiveRecord::Base
after_commit :send_email
# Remainder of model code....

Your property.councils call returns empty set.

Related

Rails5. Nested attributes are not going to database

class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :user
end
<%= form_for #user do |f| %>
.... // some filed here everything fine
<%= f.fields_for :address do |a| %>
<%= a.text_field :city %> // this field is not appear
<% end %>
<% end %>
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.valid?
#user.save
else
redirect_to root_path
end
end
private
def user_params
params.require(:user).permit(:id, :name, :email, :password, :password_confirmation, :status, :image, :address_attributes => [:id, :city, :street, :home_number, :post_code, :country])
end
end
So like you can see above I have two classes and one form, when I am trying display fields for Address class I can not do it in that way. I took this example from https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
I was trying different combination like for example using User.new and Address.new in form definition it not working as well, I was able display all fields in that situation but I wasn't able to save Address data to table, because of "unpermited address".
Can someone explain what I am doing wrong? Or at least give me please some hints.
[SOLVED]
I should learn how to read documentations properly. Excalty like #Srack said I needed just use build_address method. I checked documentation rails api again and on the end of page there was examples says to create User class like this:
class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
def address
super || build_address
end
end
and that solved my issue.
Thank you.
You'll have to make sure there's an address instantiated for the user in the new view. You could do something like:
def new
#user = User.new
#user.build_address
end
You should then see the address fields on the form.
The nested_fields_for show the fields for a record that's been initialised and belong to the parent. I think the latter is why your previous attempts haven't worked.
FYI build_address is an method generated by the belongs_to association: http://guides.rubyonrails.org/association_basics.html#methods-added-by-belongs-to

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.

Wrong posts showing up in other board

I current have my project set up like this:
resources :boards, :path => '' do
resources :posts, :path => 'thread' do
resources :replies
On /board1/ only posts from board1 show, same for board2. In /board1/thread/1/ it shows post 1 and the replies to it.
However in /board2/thread/1/ the post that is showing is from board1/thread/1/, and in the reverse board1/thread/2/ shows the post from board2/thread/2/.
Each post has a related board_id in the db, and each reply has the related post_id in the db.
How can I keep these separate?
class Board < ActiveRecord::Base
has_many :posts
has_many :replies, through: :posts
include FriendlyId
friendly_id :name, use: :slugged
accepts_nested_attributes_for :posts, :replies
end
class Post < ActiveRecord::Base
belongs_to :board
has_many :replies, dependent: :destroy
accepts_nested_attributes_for :replies
include FriendlyId
friendly_id :pid, use: :slugged
after_create :set_pid
def set_pid
post_max = self.board.posts.maximum(:pid)
reply_max = self.board.replies.maximum(:pid)
if post_max.to_i < reply_max.to_i
self.update_attributes(:pid => reply_max.to_i + 1)
else
self.update_attributes(:pid => post_max.to_i + 1)
end
end
end
Code to display post in /:board_id/show:
<% #board.posts.find_each do |post| %>
<%= post.subject %>
<%= post.name %>
<%= post.email %>
<%= post.created_at %>
No.<%= post.pid %>
<%= link_to "[reply]", board_posts_path(#board, #post)%>
<br>
<%= post.comment %><br><br>
<%= render "replies/replies" %>
<% end %>
Code to display post in /:board_id/thread/:id:
<p>
<%= #post.subject %>
<%= #post.name %>
<%= #post.email %>
<%= #post.created_at %>
No.<%= #post.pid %>
<br>
<%= #post.comment %>
</p>
Edit:
class RepliesController < ApplicationController
def create
#board = Board.friendly.find(params[:board_id])
#post = Post.friendly.find(params[:post_id])
#reply = #post.replies.create(reply_params)
redirect_to #board
end
private
def reply_params
params.require(:reply).permit(:name, :email, :subject, :comment, :pid)
end
end
class PostsController < ApplicationController
def show
#boards = Board.all
#replies = Reply.all
#post = Post.friendly.find(params[:id])
end
def create
#board = Board.friendly.find(params[:board_id])
#post = #board.posts.create(post_params)
if #post.save
redirect_to #board
else render #board
end
end
private
def post_params
params.require(:post).permit(:name, :email, :subject, :comment, :pid)
end
end
The missing part here is the RepliesController which is the source of the problem if I got the question correctly.
Most probably you have there something like #replies = current_post.replies which fetch all replies of the given post regardless of the current board. Scoping post by board will solve the problem:
current_post = Post.find_by(board_id: params[:board_id], id: params[:post_id])
if current_post
#replies = current_post.replies
end
On your friendly_id declaration in the Post model, you don't have the pid as globally unique. Use this form of friendly_id, instead:
friendly_id :pid, use: :scoped, scope: :board
In this way, duplicate friendly_id values for pid are kept separate by the board that they belong to. This is necessary for slugging nested resources properly. The :scoped value says that it's for nested (scoped) models, and the scope: key indicates that posts is nested within boards. Note that you may have to do this with replies, as well.
You'll also want to make sure that your indexes for your :slug are correct. Typically when the :scope is incorrect, you'll find it when you try to save the record. In this case, it looks like the indexes might not be set correctly to ensure the uniqueness of the board name/post pid combination. Check out Friendly ID 4 Using scoped module for more information.
When you have the indexes sorted out, you'll find that inserting new records will require you to have the friendly_id (based on your pid) already assigned. You may also want to look into using slug candidates to dynamically generate the proper slug at creation time. Also check out slug candidates rails 4 for some usage information.

How can get array values using has_and_belongs_to_many?

Can't save params selected on select box.
Table users:
1id| |name|
1 CR7
2 Messi
Table ejecutives:
1id| |name|
1 Mourinho
2 Guardiola
Table user_ejecutives:
|id| |user_id| |ejecutive_id|
1 1 1
2 2 2
Controller users_controller.rb:
def new
#obj_user = User.new
end
def create
#user = User.new user_params
#user.save
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
Models:
#User.rb
has_many :ejecutives, :through => :user_ejecutives
has_many :user_ejecutives
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives"#, foreign_key: :user_id, association_foreign_key: :ejecutive_id
#Ejecutive.rb
has_many :user_ejecutives
has_many :users, :through => :user_ejecutives
#UserEjecutive.rb
belongs_to :user
belongs_to :ejecutive
View new.html.erb:
<%= form_for #user do |f| %>
<%= form.text_field :name %>
<%= f.collection_select :user_ejecutive_ids, Ejecutive.all, :id, :name, multiple: true %>
<% end %>
View show.html.erb
<% #user.ejecutives.each do |ejecutive| %>
<%= ejecutive.name %></label>
<% end %>
I'm not getting results on the view show and it show on logs:
SystemStackError (stack level too deep):
If you're just trying to populate the join table (user_ejecutives), you'll want to populate the singular_colletion_ids method:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.collection_select :user_ejecutive_ids, User.all, :id, :name, multiple: true %>
<%= f.submit %>
<% end %>
This will assign new user_ejecutives for each new #user you create.
PS User.all is valid in this instance as you're dealing with a new (uncreated) #user record, hence it won't appear in the db.
If you wanted to create new user_ejecutives with each new #user, you'll want to use accepts_nested_attributes_for, which I can explain if required.
Update
So your error is as follows:
Unpermitted parameter: user_ejecutive_ids
... you also have another error...
NoMethodError (undefined method `each' for nil:NilClass):
This is exactly why I don't like your code. Because it doesn't fit to convention, you've go to evaluate whether the params are present etc.
You'll need to use the controller code I posted - it will populate the other table for you, and fix this NilClass error.
--
Join Table
Your user_ejecutives table is a join table.
Your User model should have the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives", foreign_key: :user_id, association_foreign_key: :ejecutive_id
end
You'll have to remove the id column from your user_ejecutives table (as per the definition here). The importance of this is that it gives you the ability to populate the singular_collection_ids method (in your case user_ejective_ids), as per my recommended code.
Try the following code.
params.require(:user).permit(:name, :user_ejecutives => [])
Hey, I think you have "ejecutive_id" column declared as integer but when loop through "user_ejecutives" you are getting each value as string, May be this is causing the issue, Kindly update your create action to below.
def create
obj_user = User.new(user_params)
if obj_user.save
params[:user_ejecutives].each do |ejecutive|
user_ejecutive = UserEjecutive.create(user_id: obj_user.id, ejecutive_id: ejecutive.to_i)
user_ejecutive.save
end
end
end

In Rails, how do I use RESTful actions for a resource that is the join in a many to many relationship?

I have the following models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :queue
end
class Queue < ActiveRecord::Base
has_many :subscriptions
end
I want to have some meta-data in the Subscription class and allow users to maintain the details of each of their subscriptions with each subscriptions meta-data. Queues produce messages, and these will be sent to users who have Subscriptions to the Queue.
As I see it the resource I want to have is a list of subscriptions, ie the user will fill in a form that has all the Queues they can subscribe to and set some metadata for each one. How can I create a RESTful Rails resource to achieve this? Have I designed my Subscription class wrong?
I presently have this in my routes.rb:
map.resources :users do |user|
user.resources :subscriptions
end
But this makes each subscription a resource and not the list of subscriptions a single resource.
Thanks.
This can be done quite easily using accepts_nested_attributes_for and fields_for:
First in the User model you do the following:
class User < ActiveRecord::Base
has_many :subscriptions
accepts_nested_attributes_for :subscriptions, :reject_if => proc { |attributes| attributes['queue_id'].to_i.zero? }
# if you hit scaling issues, optimized the following two methods
# at the moment this code is suffering from the N+1 problem
def subscription_for(queue)
subscriptions.find_or_initialize_by_queue_id queue.id
end
def subscribed_to?(queue)
subscriptions.find_by_queue_id queue.id
end
end
That will allow you to create and update child records using the subscriptions_attributes setter. For more details on the possibilities see accepts_nested_attributes_for
Now you need to set up the routes and controller to do the following:
map.resources :users do |user|
user.resource :subscriptions # notice the singular resource
end
class SubscriptionsController < ActionController::Base
def edit
#user = User.find params[:user_id]
end
def update
#user = User.find params[:user_id]
if #user.update_attributes(params[:user])
flash[:notice] = "updated subscriptions"
redirect_to account_path
else
render :action => "edit"
end
end
end
So far this is bog standard, the magic happens in the views and how you set up the params:
app/views/subscriptions/edit.html.erb
<% form_for #user, :url => user_subscription_path(#user), :method => :put do |f| %>
<% for queue in #queues %>
<% f.fields_for "subscriptions[]", #user.subscription_for(queue) do |sf| %>
<div>
<%= sf.check_box :queue_id, :value => queue.id, :checked => #user.subscribed_to?(queue) %>
<%= queue.name %>
<%= sf.text_field :random_other_data %>
</div>
<% end %>
<% end %>
<% end %>
I found this tutorial very useful, as I was trying to relate Users to Users via a Follows join table: http://railstutorial.org/chapters/following-users

Resources