Simple rails app: I have 2 models, user and intro [which is simply a message]. Each message has a sender (user) and receiver (user). Here's the intro model (validations omitted):
class Intro < ActiveRecord::Base
attr_accessible :content
belongs_to :sender, class_name: "User"
belongs_to :receiver, class_name: "User"
default_scope order: 'intros.created_at DESC'
end
and now the user model:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
has_many :sent_intros, foreign_key: "sender_id", dependent: :destroy, class_name: "Intro"
has_many :received_intros, foreign_key: "receiver_id", dependent: :destroy, class_name: "Intro"
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
The app currently lets the current user submit an intro into a form and associate with that message (a home page shows sent_intros). However, I could use some help in the intros_controller/create method when it comes to the received_intros function. How do I let an intro that is created by the current user be associated with (i.e. sent to) another specific user so that I can route it to a recipient's inbox? Thank you.
class IntrosController < ApplicationController
before_filter :signed_in_user
def create
#sent_intro = current_user.sent_intros.build(params[:intro])
if #sent_intro.save
flash[:success] = "Intro sent!"
redirect_to root_path
else
render 'static_pages/home'
end
end
def index
end
def destroy
end
end
It doesn't look like you're allowing the current_user to assign a receiver to an intro they create? You need to have an input on your form that allows a user to set a valid receiver_id, and you need to add receiver_id to attr_accessible:
class Intro < ActiveRecord::Base
attr_accessible :content, :receiver_id
#Rest of your code
end
With that, when your intro is created, it will be properly associated with both a sender and a receiver. You would then be able to access a current_user's received intros with the method current_user.received_intros
You may want to add some validation to the Intro model to make sure both a receiver and a sender exist.
EDIT: You can add the receiver_id field to your code in the comments like so:
<!-- In your first view -->
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<%= #users.each do |user| %>
<%= render user %>
<%= render 'shared/intro_form', :user => user %> <!-- make sure you pass the user to user intro_form -->
<% end %>
</ul>
<%= will_paginate %>
<!-- shared/intro_form -->
<%= form_for(#sent_intro) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Shoot them an intro..." %>
</div>
<%= observe_field :intro_content, :frequency => 1, :function => "$('intro_content').value.length" %>
<%= f.hidden_field :receiver_id, :value => user.id %> <!-- Add this to pass the right receiver_id to the controller -->
<%= f.submit "Send intro", class: "btn btn-large btn-primary" %>
<% end %>
Related
I'm building my first semi "real world" rails app (an Open House management app for real estate). I am currently stuck on a "accepts nested attributes" problem. Here are the details... (I've provided all code below)
An open house can have many contacts (attendees), and a contact can attend many open houses. So, I have a join model called Signin. When someone "signs in" to an open house via the Open House > Sign In form, I would like a "sign in" instance to be created along with a contact via nested attributes. Note: Since this is a "has many through", the "Sign in" and "contact" models are not the traditional "parent/child" relationship.
When attempting to submit my Signin form, the form error says "Contact must exist" and the log reports "Unpermitted parameter: :contacts". (I do have strong parameter sit, but perhaps incorrectly).
At this point, I just feel like I've tried everything, but I can't get the form to create a Contact as well as a Signin instance.
The relevant models:
class OpenHouse < ApplicationRecord
belongs_to :listing
has_many :signins, inverse_of: :open_house
has_many :contacts, through: :signins
class Signin < ApplicationRecord
belongs_to :open_house
belongs_to :contact
accepts_nested_attributes_for :contact
class Contact < ApplicationRecord
has_many :signins
has_many :open_houses, through: :signins
Routes: (Signins are nested within each open house to allow easy sharing of sign in form)
resources :open_houses do
resources :signins
end
resources :contacts
The Signin Controller:
class SigninsController < ApplicationController
def new
#open_house = OpenHouse.find(params[:open_house_id])
#signin = #open_house.signins.build
end
def create
#open_house = OpenHouse.find(params[:open_house_id])
#signin = #open_house.signins.new(signin_params)
if #signin.save
redirect_to #signin, notice: 'You are now signed in'
else
render :new
end
end
def show
#signin = Signin.find(params[:id])
end
private
def signin_params
params.require(:signin).permit( contacts_attributes: [:name, :email, :phone])
end
end
Signin Form:
<%= form_with model: [ #open_house, #open_house.signins.build ], local: true do |f| %>
<% if #signin.errors.any? %>
<% #signin.errors.full_messages.each do |msg| %>
<%= msg %>
<% end %>
<% end %>
<%= f.fields_for :contacts do |c| %>
<%= c.label :name %>
<%= c.text_field :name, placeholder: "Your name", class: "form-control" %>
<%= c.label :email %>
<%= c.text_field :email, placeholder: "Your email", class: "form-control" %>
<%= c.label :phone %>
<%= c.text_field :phone, placeholder: "Your phone", class: "form-control" %>
<% end %>
<%= f.submit 'Sign In', class: "btn btn-primary" %>
<% end %>
Its a simple pluralization error. You need to use f.fields_for :contact not :contacts.
I'm not too familiar with form_with - so the above may be a better solution, but traditionally you can use multiple fields_for. This will always create a new signing and contact - if that is your intention.
accepts_nested on OpenHouse may be required regardless
class OpenHouse < ApplicationRecord
has_many :signins
accepts_nested_attributes_for :signins
= f.fields_for :signins, [f.object.signins.build] do |s|
= s.fields_for :contract, Contact.new do |c|
house = OpenHouse.find(params.require(:open_house_id))
if house.update(openhouse_params)
# success
...
I'm developing an app for college where a user can log on & upload details of a hiking trail.
So far everything is working & I have also implemented a nested form for photos in each hiking trail. A user can log-on & create a hike.
I would like to display all the hikes which the user created in there show/profile page but when I've set up the relationships in my database & the has_many & belongs_to options in my model. I've also tried to do this with nested accepts_nested_attributes_for :hikingtrails it does none of this works.
I've checked my database when a hikingtrail is created by a user it is not updating the user_id field in the table.
I'm not sure if I'm approaching this entirely the wrong way, should I be looking at polymorphic associations?
class User < ActiveRecord::Base
attr_accessible :user_name, :email, :password, :password_confirmation, :photos_attributes, :hikingtrails_attributes
has_many :hikingtrails
accepts_nested_attributes_for :hikingtrails, :allow_destroy => :true, :reject_if => :all_blank
class Hikingtrail < ActiveRecord::Base
attr_accessible :description, :name, :looped, :photos_attributes,:directions_attributes, :user_id
has_many :photos
has_many :trails
has_many :directions
belongs_to :user
users/show.html.erb
<div class="page-header">
<h1>Your Profile</h1>
</div>
<p>
<b>username:</b>
<%= #user.user_name %>
</p>
<p>
<b>email:</b>
<%= #user.email %>
</p>
<h4>Small Photos</h4>
<% #user.photos.each do |photo| %>
<%= image_tag photo.image_url(:thumb).to_s %>
<% end %>
<h4>Hiking Trails</h4>
<% #user.hikingtrails.each do |hk| %>
<%= hk.name %>
<% end %>
<%= link_to "Edit your Profile", edit_user_path(current_user), :class => 'btn btn-mini' %>
You didn't add :user_id to your accessible attributes in the Hikingtrail model. Try the following:
attr_accessible :description,
:duration_hours,
:duration_mins,
:name,
:looped,
:addr_1,
:addr_2,
:addr_3,
:country,
:latitude,
:longitude,
:photos_attributes,
:trails_attributes,
:directions_attributes,
:user_id
UPDATE:
After seeing the form code, I think it's probably not necessary to do the above and could potentially also be unsafe. Instead, don't set the user_id through mass assignment, but handle user assignment in your controller like so:
class HikingtrailsController < ApplicationController
# ...
def create
#hikingtrail = Hikingtrail.new(params[:hikingtrail])
#hikingtrail.user = current_user
if #hikingtrail.save
# ...
else
# ...
end
end
end
Hope this helps :)
I've searched everywhere for a solution but haven't come up with any.
The part that works: My app allows customers to create an account using a nested form. The data collected creates records in four models - accounts, users, accounts_users (because a user can be associated with many accounts), and profile (to store the user's fname, lname, phone, etc).
That part that doesn't work: Once logged in, I want the users to be able to add more users to their account using the form below. I don't receive any errors upon submit but I am brought back to the same form with no additional records created. Any help would be awesome!
Here is the nested form...
<%= form_for #user, :validate => true do |f| %>
<fieldset>
<%= f.fields_for :profile do |p| %>
<div class="field">
<%= p.label :first_name %>
<%= p.text_field :first_name %>
</div>
<div class="field">
<%= p.label :last_name %>
<%= p.text_field :last_name %>
</div>
<div class="field">
<%= p.label :phone %>
<%= p.text_field :phone %>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Create New User', :class => "btn btn-large btn-success" %>
<%= cancel %>
</div>
</fieldset>
The ApplicationController scopes everything to the current_account like so:
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain) if request.subdomain
end
The UsersController
def new
#user = User.new
#user.build_profile()
#current_account.accounts_users.build() #Edit2: This line was removed
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
def create
#user = User.new(params[:user])
#user.accounts_users.build(:account_id => current_account.id) #Edit2: This line was added
if #user.save
# Send Email and show 'success' message
flash[:success] = 'An email has been sent to the user'
else
# Render form again
render 'new'
end
end
Models look like this:
class Account < ActiveRecord::Base
attr_accessible :name, :subdomain, :users_attributes
has_many :accounts_users
has_many :users, :through => :accounts_users
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :profile_attributes
has_many :accounts_users
has_many :accounts, :through => :accounts_users
has_one :profile
accepts_nested_attributes_for :profile
end
class AccountsUser < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
class Profile < ActiveRecord::Base
belongs_to :user
attr_accessible :first_name, :last_name, :phone
end
Edit2: It turns out that I had required a password + password_comfirmation validation in the User model which prevented me from adding another user without these fields. I commented out these validations plus removed the line: current_account.accounts_users.build() in the 'new' action and added the line: #user.accounts_users.build(:account_id => current_account.id) in the 'create' action.
"I want the users to be able to add more users to their account using the form below." I assume you mean profiles (since your nested form is on profiles)?
If that's the case, I think your UsersController's create action isn't associating the profiles with users by using new.
Try this...
def new
#user = User.build
#profile = #user.profiles.build #build adds the profile to user's associated collection of profiles, but new doesn't
...
end
def create
#user = User.build(params[:user])
if #user.save
....
end
end
If you want the user to be associated with account, then you need to put the new and create actions in the AccountsController and do something similar to nest association of the users and profiles records.
Btw, the reason that it went back to new is because you render new at the end of the create, in case that's also part of the question. Hope that helps!
We have the following code working for a complex rails form with checkboxes. I'm not really happy with the solution we have in place and I was wondering if anyone knows of a more proper way to do this in rails. All the code below is working I just want to know if there is a cleaner approach.
In my Admins controller I want to remove the need to call the following code on each update.
#user.admin.school_admin_roles.destroy_all
params[:roles].each do |school_role|
ids = school_role.split('_')
#user.admin.school_admin_roles.find_or_create_by_school_id_and_school_role_id(ids[0], ids[1])
end if !params[:roles].nil?
So I basically want to be able to call #user.update_attributes(params[:user]) and have rails take care of creating the needed relationships for me. I have that working with AccountRole in the form below. I want to know if there is a way to do the same thing with SchoolRole given I have an extra variable school_id in the join table.
We have the following form for editing a user and assigning roles
Screenshot of form ->
http://i.stack.imgur.com/PJwbf.png
I have the following form where an admin can edit other users and assign account based roles and school based roles via checkboxes. The account based roles were easy to implement. The school based rules are a bit complicated since the join table school_admin_roles has school_id, user_id, role_id fields. We had to implement the school roles part of the form in a rather hackish way. We have the form implemented like this - notice how we hacked together school.id.to_s+'_'+role.id.to_s into the same checkbox on school roles.
In the Admins controller's update function we manually destroy all school_admin roles on each update then loop through the school roles params do a split on the ids on '-' then manually re-create each school based role. I really hate the way we've had to go about this. Could anyone shed some light on a cleaner more rails centric approach to solving this scenario?
The form -
<%= form_for #user, :url => {:controller => 'admins', :action => 'update'} do |f| %>
<%= f.label :username %>
<%= f.text_field :username %>
<%= f.fields_for :admin do |uf| %>
<div class="field">
<%= uf.label :first_name %>
<%= uf.text_field :first_name %>
</div>
<label>Admin Permissions</label>
#account level permissions works fine
<%= hidden_field_tag "#{uf.object_name}[account_role_ids][]" %>
<% AccountRole.find(:all).each do |role| %>
<div class="account_role">
<%= check_box_tag "#{uf.object_name}[account_role_ids][]", role.id, #user.admin.account_roles.include?(role)%>
<%= role.name %>
</div>
<% end %>
#school level permissions a bit of a hack
<%= hidden_field_tag "#{uf.object_name}[school_role_ids][]" %>
<% SchoolRole.find(:all).each_with_index do |role, index| %>
<div class="school_role">
<%= check_box_tag "#{uf.object_name}[school_role_ids][]",role.id, #user.admin.school_roles.include?(role) %>
<%= role.name %>
<span class="advanced_box admin_permissions" <% if #user.admin.school_roles.include?(role) %>style="display:inline"<% end %>>
<div class="content" id="perm_<%= index %>">
<h4><%= role.name %></h4>
<% uf.object.account.schools.each do |school|%>
<div>
<%= check_box_tag "roles[]", school.id.to_s+'_'+role.id.to_s, role.school_admin_roles.where(:admin_id => uf.object.id).collect(&:school_id).include?(school.id)%>
<%= school.name %>
</div>
<% end %>
<%= link_to 'Done', '#', :class => "done" %>
</div>
Advanced
</span>
</div>
<% end %>
</div>
<% end %>
The controller
class AdminsController < ApplicationController
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
# TODO find a way to refactor this
#user.admin.school_admin_roles.destroy_all
params[:roles].each do |school_role|
ids = school_role.split('_')
#user.admin.school_admin_roles.find_or_create_by_school_id_and_school_role_id(ids[0], ids[1])
end if !params[:roles].nil?
#
flash[:notice] = "Successfully updated Admin."
redirect_to admins_path
else
render "edit"
end
end
end
Given the following models
class User < ActiveRecord::Base
has_one :parent
has_one :admin
has_many :scool_admin_roles
has_many :account_admin_roles
end
class AccountAdminRole < ActiveRecord::Base
before_save :set_account_id
belongs_to :admin
belongs_to :account_role
end
class SchoolAdminRole < ActiveRecord::Base
belongs_to :admin
belongs_to :school_role
belongs_to :school
end
class SchoolRole < ActiveRecord::Base
has_many :school_admin_roles
end
class AccountRole < ActiveRecord::Base
has_many :account_admin_role
end
When I face code that I know smells bad, usually it leads me to the design.
In this case, the problem is the database table design.
You are hacking the value passed from a checkbox with a delimiter because the "join" table does more than just join. I believe that the relationship to school belongs_to the SchoolRole and not the SchoolAdminRole. Changing this will create a pattern much like your AccountRole.
Correcting the model design, might be a bit painful now, but it is much cleaner and will be maintainable in the future. You will thank yourself later.
We refactored the code above as follows
In the model we added accepts_nested_attributes_for :school_admin_roles, :reject_if => proc { |attr| attr['school_role_id'].blank? }
and added school_admin_roles_attributes to attr_accessible
class Admin < ActiveRecord::Base
belongs_to :account
belongs_to :user
has_many :school_admin_roles
has_many :school_roles, :through => :school_admin_roles
has_many :account_admin_roles
has_many :account_roles, :through => :account_admin_roles
accepts_nested_attributes_for :account
accepts_nested_attributes_for :school_admin_roles, :reject_if => proc { |attr| attr['school_role_id'].blank? }
attr_accessible :account_role_ids, :email, :first_name, :last_name, :account_id, :user_id, :account_attributes, :school_admin_roles_attributes
default_scope where(:deleted => false)
end
We then built the form as follows
<% index2 = 0 %>
<% SchoolRole.find(:all).each_with_index do |role, index| %>
<div class="school_role">
<%= check_box_tag "school_roles[]",role.id, #user.admin.school_roles.include?(role) %>
<%= role.name %>
<span class="advanced_box admin_permissions" <% if #user.admin.school_roles.include?(role) %>style="display:inline"<% end %>>
div class="content" id="perm_<%= index %>">
<h4><%= role.name %></h4>
<% uf.object.account.schools.each do |school|%>
<div>
<%= check_box_tag "#{uf.object_name}[school_admin_roles_attributes][#{index2}][school_role_id]", role.id, role.school_admin_roles.where(:admin_id => uf.object.id).collect(&:school_id).include?(school.id)%>
<%= school.name %>
<%= hidden_field_tag "#{uf.object_name}[school_admin_roles_attributes][#{index2}][school_id]", school.id %>
</div>
<% index2 += 1 %>
<% end %>
<%= link_to 'Done', '#', :class => "done" %>
</div>
Advanced
</span>
</div>
<% end %>
</div>
<% end %>
Which then enabled us to refactor the controller without splitting the ids but we still have to call destroy all each time which I can live with.
def update
#user = User.find(params[:id])
#user.admin.school_admin_roles.destroy_all
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated Admin."
redirect_to admins_path
else
render "edit"
end
end
I am trying to get my user form to also allow the user to fill out their company profile at the same time via form_for. For some reason it is not showing the company fields. Here is my code for the controller and layouts.
class User < ActiveRecord::Base
attr_accessible :company_attributes
has_one :company
accepts_nested_attributes_for :company
end
class Company < ActiveRecord::Base
belongs_to :user
# Validation
validates :name, :presence => true
end
<%= f.fields_for :company do |company_form| %>
<div class="field">
<%= company_form.label :name, "Company Name" %><br />
<%= company_form.text_field :name %>
</div>
<% end %>
The company attribute of the User should be not-nil, so either in the controller or in the form, create it:
<% user.build_company if user.company.nil? %>
<%= f.fields_for :company do |company_form| %>
...
It might be better to do this in the model rather than the view or the controller.
class User
# Blah blah blah
def profile
super || build_profile
end
end
The above solution from Zabba only worked for me with:
<% #user.build_profile if #user.profile.nil? %>
Othwerise, the view had no idea what "user" is