Rails 4 - setting associated foreign key value - ruby-on-rails

I have a model called project and another called invite.
The associations are:
Project
has_many :invites
Invite
belongs_to :project
I want to allow users who create projects to send invites to other users to join them in working on the project.
I have a form to create invites as follows:
<%= simple_form_for(#invite, :url => invites_path) do |f| %>
<%= f.hidden_field :recipient_id, :value => get_recipient_id %>
<%#= f.hidden_field :project_id, :value => current_user.profile.project_id %>
<%= f.hidden_field :project_id, :value => #invite.project_id %>
<%= f.label :email %>
<%= f.email_field :email %>
<!-- This field should use :date_picker- but it isnt working -->
<%= f.input :expiry, :as => :date, :label => "When do you need a response to this invitation?" %>
<%#= f.input :expiry, :as => :date_picker, :label => "When do you need a response to this invitation?" %>
<%= f.submit 'Send', :class=>"formsubmit" %>
<% end %>
In the above form, I'm trying to set the project_id value.
In the invites controller, I have:
class InvitesController < ApplicationController
before_action :get_project
def index
#invites = #project.invites
end
def new
# #invite = Invite.new
#invite = #project.invites.build
end
def create
#invite = Invite.new(invite_params)
#invite.sender_id = current_user.profile.id
# #project = Project.find(params[:project_id])
if #invite.save
#send existing user email invitation to join project team
InviteMailer.existing_user_invite(#invite).deliver_later
format.html { redirect_to #project }
#invite.recipient.project.push(#invite.project)
else
#invite.recipient.project.push(#invite.project)
InviteMailer.new_user_invite(#invite, new_user_registration_path(:invite_token => #invite.token)).deliver
end
# oh no, creating an new invitation failed
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invite
#invite = Invite.find(params[:id])
end
def get_project
#project = Project.find(params[:project_id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def invite_params
params[:invite].permit(:email, :project_id, :recipient_id)
end
end
I thought the get_project before action would get the project. Instead, I get an error that says:
undefined method `project_id' for #<Profile:0x007fa179509b60>
Did you mean? project_ids
project_ids=
projects
object_id
The error message points to the hidden field in my form for project_id.
Can anyone see where I'm going wrong?

The problem is with the statement current_user.profile.project_id in one of the hidden form fields.
(The error message points to this code but I don't know why you have commented that line in the post)
The error clearly says that a profile does not have a project_id and a user can have many projects. If you want to access the project_id, you can do something like this.
<%= f.hidden_field :project_id, value: #project.id %>
#project is accessible from the controller through the get_project method.

Related

Saving a list of emails from a form-text input into Models email_list attribute (array)

My goal is to when adding a new product with the new product form, to have an input where one can add a list of emails separated by a space. The list of emails in this string field would be saved as an array of emails in the email_list array attribute of the Product model. This way each product has many emails. (later an email will be sent to these users to fill out questionaire, once a user fills it out there name will be taken off this list and put on completed_email_list array.
I am relatively new to rails, and have a few questions regarding implementing this. I am using postgresql, which from my understanding I do not need to serialize the model for array format because of this. Below is what I have tried so far to implement this. These may show fundamental flaws in my thinking of how everything works.
My first thinking was that I can in my controllers create action first take params[:email].split and save that directly into the email_list attribute (#product.email_list = params[:email].split. It turns out that params[:email] is always nil. Why is this? (this is a basic misunderstanding I have)(I put :email as accepted param).
After spending a long time trying to figure this out, I tried the following which it seems works, but I feel this is probably not the best way to do it (in the code below), which involves creating ANOTHER attribute of string called email, and then splitting it and saving it in the email_list array :
#product.email_list = #product.email.split
What is the best way to actually implement this? someone can clear my thinking on this I would be very grateful.
Cheers
Products.new View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.input :email %>
<%= f.button :submit %>
<%end %>
Products Controller
class ProductsController < ApplicationController
before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
if params[:category].blank?
#products= Product.all.order("created_at DESC")
else
#category_id=Category.find_by(name: params[:category]).id
#products= Product.where(:category_id => #category_id).order("created_at DESC")
end
end
def new
#product=current_user.products.build
#categories= Category.all.map{|c| [c.name, c.id]}
end
def show
end
def edit
#categories= Category.all.map{|c| [c.name, c.id]}
end
def update
#product.category_id = params[:category_id]
if #product.update(product_params)
redirect_to product_path(#product)
else
render 'new'
end
end
def destroy
#product.destroy
redirect_to root_path
end
def create
#product=current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = #product.email.split
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:product).permit(:title, :description, :category_id, :video, :thumbnail,:email, :email_list)
end
def find_product
#product = Product.find(params[:id])
end
end
To solve your original issue
#product.email_list = params[:email].split. It turns out that params[:email] is always nil
:email is a sub key of :product hash, so it should be:
#product.email_list = params[:product][:email].split
Demo:
params = ActionController::Parameters.new(product: { email: "first#email.com last#email.com" })
params[:email] # => nil
params[:product][:email] # => "first#email.com last#email.com"
I'd say that what you have is perfectly fine, except for the additional dance that you're doing in #product.email_list=#product.email.split, which seems weird.
Instead, I'd have an emails param in the form and an #emails= method in the model (rather than email and #email=):
def emails=(val)
self.email_list = val.split
end
Alternatively, you could do that in the controller rather than having the above convenience #emails= method, similar to the way you're handling the category_id:
#product = current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = product_params[:emails].split
Because you need validations on your emails and to make it cleaner I would create an email table, make Product table accept Email attribues and use cocoon gem to have a nice dynamic nested form with multiple emails inputs.
1) models
class Product < ActiveRecord::Base
has_many :emails, dependent: :destroy
accepts_nested_attributes_for :emails, reject_if: :all_blank, allow_destroy: true
end
class Email < ActiveRecord::Base
belong_to :product
validates :address, presence: true
end
2) Controller
class ProductsController < ApplicationController
def new
#product = current_user.products.build
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:project).permit(:title, :description, :category_id, :video, :thumbnail, emails_attributes: [:id, :address, :_destroy])
end
end
3) View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.association :category %>
<div id="emails">
<%= f.simple_fields_for :emails do |email| %>
<%= render 'emails_fields', f: email %>
<div class="links">
<%= link_to_add_association 'add email', f, :emails %>
</div>
<%= end %>
</div>
<%= f.button :submit %>
<% end %>
In your _emails_fields partial:
<div class="nested-fields">
<%= f.input :address %>
<%= link_to_remove_association "Remove email", f %>
</div>
Then setup cocoon's gem and javascript and you'll be good.
Reference: https://github.com/nathanvda/cocoon

Check_box_tag doesn't retain value in rails app

I have a model called "Clients". The Clients model belongs to the Users model (Users model is associated with devise). There is another model called "sellers", but they aren't involved in the question. The Client can do payments to me manually (cash only). When the client does this payment, I give them access to more pages in the website. To do this, I added a boolean variable called "paid" to my clients and then the admin(me) can go to their client profile, update the paid status from 'unpaid' to 'paid' through a checkbox. Only the admin can view the checkbox.
This is the form partial for updating client information:
<%= simple_form_for #client do |f| %>
<%= f.input :name, label: 'Full name', error: 'Full name is mandatory' %>
<%= f.input :company, label: 'Name of company' %>
<%= f.input :position, label: 'Position you hold at your company' %>
<%= f.input :number, label: 'Phone number' %>
<%= f.input :email, label: 'Enter email address' %>
<%= f.file_field :emp_img, label: 'Profile picture' %>
<%= check_box_tag 'paid', 'yes', false %>
<%= f.button :submit %>
<% end %>
Then my client controller is:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
if current_user.user_type == :admin
params[:client][:paid] = params[:client][:paid] == 'yes'
params.require(:client).permit(:paid, :name, :company, :position, :number, :email, :client_img)
else
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
end
def find_client
#client = Client.find(params[:id])
end
end
When I go to the client profile, and click on "update" info, I get the form partial, in that, the check box is not checked. I click it and update the profile, no errors, takes me back to the profile. But when I click update again, the check box is unchecked. It doesn't retain the value of the check box ONLY. Everything else is retained. Like the name, company and all that. When I go into the rails c, the paid variable is still false even if I click on it and update it. Does anyone know why it might be so?
Use check_box form helper in place of check_box_tag into the form, and remove line from client_params method:
params[:client][:paid] = params[:client][:paid] == 'yes'

Rails 5: fields_for nested_attributes does not save (fails at create controller)

I am trying to work in my first implementation using fields_for to manage creating has_many relationship in one form partial. This form partial itself is part of a nested resource
So far, I am able to render, save and edit the form successfully without the fields_for nested form.
When I include the fields_for in the form_for, white-list the params, and build the objects in #new, I get this error in the console as it failed to save and re renders the #new view:
(0.1ms) rollback transaction
What can I do to successfully save the form along with the nested_attributes?
routes.rb
....
resources :projects do
resources :step_ones
resources :step_threes
resources :step_twos
resources :step_fours
resources :step_fives
resources :timelines
end
step_four.rb
class StepFour < ApplicationRecord
belongs_to :project
has_many :ios_devices
accepts_nested_attributes_for :ios_devices
end
ios_device.rb
class IosDevice < ApplicationRecord
belongs_to :step_four
end
_form.html.erb
<div>
<%= form_for([#project, #step_four]) do |f| %>
....
<%= f.fields_for :ios_devices do |d| %>
<div class='form-group'>
<%= d.label :full_name, "Name:"%>
<%= d.text_field :full_name %>
<%= d.label :email, "Email:"%>
<%= d.text_field :email %>
<%= d.label :udid, "UDID:"%>
<%= d.text_field :udid %>
<% end %>
<%= hidden_field_tag :project_id, :value => #project.id %>
<div class='row'>
<span class='col-md-6'><%= button_to "Back", project_path(#project), method: :get, class:'btn btn-primary full-wide-button main-btn' %></span>
<span class='col-md-6'><%= f.submit 'Save Data', class: 'btn btn-primary full-wide-button'%></span>
</div>
<% end %>
</div>
step_fours_controller.rb
class StepFoursController < ApplicationController
def new
#project = Project.find(params[:project_id])
#step_four = StepFour.new
3.times { #step_four.ios_devices.build }
end
def create
#step_four = StepFour.new(step_four_params)
#project = Project.find(params[:project_id])
#step_four.ios_devices.each do |d|
puts d.full_name
puts d.email
puts d.udid
end
#step_four.project_id = params[:project_id]
if #step_four.save
flash[:success] = "Step Five Data Saved"
redirect_to #project
else
flash[:danger] = "Data Not Saved. Please Try Again"
render "new"
end
end
def show
#step_four = StepFour.where(project_id: (params[:project_id])).first
end
def update
#step_four = StepFour.where(project_id: (params[:project_id])).first
#project = Project.find(params[:project_id])
if #step_four.update_attributes(step_four_params)
flash[:success] = "Step Four Data Saved"
redirect_to #project
else
flash[:danger] = "Data Not Saved. Please Try Again"
render 'edit'
end
end
def edit
#step_four = StepFour.where(project_id: (params[:project_id])).first
#project = Project.find(params[:project_id])
end
def step_four_params
params.require(:step_four).permit(:iphone_name, :iphone_nickname, :android_name, ios_devices_attributes: [:id, :full_name, :email, :udid])
end
end
After realizing that the error was upon the save method, I tried to force the issue and raise an exception with a shebang ! . I received a Validation error that lead me to this question:
Error: Validation failed: Images imageable must exist , rails-5.0 , paperclip-5
I needed to add optional: true to the belongs_to: step_four in the ios_device model since, I believe that the object doesn't exist yet.
Now everything is working

Best practice in creating belongs_to object

Let's say we have the following situation:
class User < ActiveRecord::Base
has_many :tickets
end
class Ticket < ActiveRecord::Base
belongs_to :user
end
For simplicity let's say Ticket has only some text field description and integer user_id. If we open User's views/users/show.html.erb view and inside User controller we have this code which finds correct user which is selected:
def show
#user = User.find(params[:id])
end`
Now inside that show.html.erb view we also have small code snipped which creates user's ticket. Would this be a good practice in creating it?
views/users/show.html.erb
<%= simple_form_for Ticket.new do |f| %>
<%= f.hidden_field :user_id, :value => #user.id %>
<%= f.text_area :description %>
<%= f.submit "Add" %>
<% end %>
controller/tickets_controller.rb
def create
#ticket = Ticket.new(ticket_params)
#user = User.find(ticket_params[:user_id])
#ticket.save
end
def ticket_params
params.require(:ticket).permit(:user_id, :description)
end
So, when we create a ticket for user, ticket's description and his user_id (hidden field inside view) are passed to tickets_controller.rb where new Ticket is created.
Is this a good practice in creating a new object which belongs to some other object? I am still learning so I would like to make this clear :) Thank you.
You should be able to do something like this in your form:
<%= f.association :user, :as => :hidden, :value => #user.id %>
This will pass user_id through your controller to your model and automatically make an association. You no longer need the #user= line in your controller.
Don't forget that the user could modify the form on their end and send any id they want. :)
See https://github.com/plataformatec/simple_form#associations for more info.
How about getting the user from the controller using current_user so that you protect yourself from anyone that would manipulate the value of the user_id in the form. Also I think this way is much cleaner
views/users/show.html.erb
<%= simple_form_for Ticket.new do |f| %>
<%= f.text_area :description %>
<%= f.submit "Add" %>
<% end %>
controller/tickets_controller.rb
def create
#ticket = Ticket.new(ticket_params)
#ticket.user = current_user
#ticket.save
end
def ticket_params
params.require(:ticket).permit(:user_id, :description)
end

Rails 3: Couldn't find User without an ID

Hi I'm currently working on my first rails project (a photo uploading site), and am getting an error on a form that creates a new photo album that belongs to a user. I'm trying to use the Paperclip gem. It's giving me the error:
ActiveRecord::RecordNotFound in AlbumsController#create
Couldn't find User without an ID
I'm guessing it has something to do with the instance variables in my create action, but I don't see what's wrong. Any guidance would be helpful. Here are my files:
_form.html.erb
<%= form_for (#album), :remote => true, :html => { :id => "uploadform", :multipart => true } do |f| %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.fields_for :avatar do |avatar_form| %>
<%= avatar_form.label :avatar, "Upload" %>
<%= avatar_form.file_field :avatar, :multiple => true %>
<% end %>
<%=f.submit %>
</div>
<% end %>
albums_controller create action:
def create
#users = User.all
#user = User.find(params[:user_id])
#album = #user.albums.build(params[:album])
if #album.save
flash[:success] = "Album created!"
end
end
config/routes
Pholder::Application.routes.draw do
resources :users do
resources :albums
end
resources :albums do
resources :pictures
end
Album model
class Album < ActiveRecord::Base
attr_accessible :avatar, :name, :description
has_and_belongs_to_many :users
has_attached_file :avatar
end
Let me know if you need to see any other files.
Since this is a new album, there is no user_id in the albums hash as I first thought. I added a new method that merges in the user id to the hash.
def create
#users = User.all
#album = Album.new(params[:album].merge!(:user_id => current_user))
if #album.save
flash[:success] = "Album created!"
end
end
If you would rather not have user_id in attr_accessible in your Album model, you can assign it separately.
def create
#users = User.all
#album = Album.new(params[:album])
#album.user_id = current_user.id
if #album.save
flash[:success] = "Album created!"
end
end

Resources