Background:
I have two models User(Devise model) and Client. I have setup associations such that Client belongs_to :user and User has_one :client. I have setup my database such that the clients table has a user_id attribute
Problem:
In my navbar, once a user is signed in, I want them to be able to access their "profile" which is represented by the Client model, however I cannot seem to access the proper client through the user_params. For example <%= link_to "My Profile", client_path(#user) %>
This results in the error No route matches {:action=>"show", :controller=>"clients", :id=>nil} missing required keys: [:id] even though the :id should be available since the user is signed in.
I have edited the Devise sessions_controller to contain:
def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit( :email, :password, :id)
end
end
...but still have no luck. I have a feeling I'm missing something basic, I just need some help.
I'm missing something basic
You don't need #user, it needs to be current_user:
<%= link_to "Profile", client_path(current_user) if user_signed_in? %>
We use a similar pattern (User has_one Profile); we made a singular resource which negates the need to pass an object:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :client
accepts_nested_attributes_for :client
end
#config/routes.rb
resource :profile, only: [:show, :update] #-> url.com/profile
#app/controllers/profile_controller.rb
class ProfileController < ApplicationController
def update
current_user.update user_params
end
private
def user_params
params.require(:user).permit(client_attributes: [:x, :y, :z])
end
end
#app/views/profile/show.html.erb
<%= form_for current_user do |f| %>
<%= f.fields_for :client do |c| %>
<%= c.text_field :x %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to send requests to... <%= link_to "Profile", profile_path %>.
Because you're only allowing the profile view for each user, you'll be able to call the current_user object for it.
Related
Working with nested routes and associations. I have a partial which creates a tenant, but after the creation it stays with the form rendered and the url changes to /tenants. Desired behavior is that it needs to redirect_to the show page. Routes are as follows:
Rails.application.routes.draw do
devise_for :landlords
authenticated :landlord do
root "properties#index", as: "authenticated_root"
end
resources :tenants
resources :properties do
resources :units
end
root 'static#home'
end
So far the properties and units work (and the landlord) Issue is with Tenants. Originally I had Tenants nested under units, but had issues there as well. Partial looks like this:
<%= form_for #tenant do |f| %>
<%= f.label "Tenant Name:" %>
<%= f.text_field :name %>
<%= f.label "Move-in Date:" %>
<%= f.date_field :move_in_date %>
<%= f.label "Back Rent Amount:" %>
$<%= f.text_field :back_rent %>
<%= f.button :Submit %>
<% end %>
<%= link_to "Cancel", root_path %>
Tenants Controller looks like this:
before_action :authenticate_landlord!
#before_action :set_unit, only: [:new, :create]
before_action :set_tenant, except: [:new, :create]
def new
#tenant = Tenant.new
end
def create
#tenant = Tenant.new(tenant_params)
if #tenant.save
redirect_to(#tenant)
else
render 'new'
end
end
def show
end
def edit
end
def update
if #tenant.update(tenant_params)
redirect_to unit_tenant_path(#tenant)
else
render 'edit'
end
end
def destroy
end
private
def set_property
#property = Property.find(params[:property_id])
end
def set_unit
#unit = Unit.find(params[:unit_id])
end
def set_tenant
#tenant = Tenant.find(params[:id])
end
def tenant_params
params.require(:tenant).permit(:name, :move_in_date, :is_late, :back_rent, :unit_id)
end
end
Models have associations:
class Tenant < ApplicationRecord
belongs_to :unit, inverse_of: :tenants
end
class Unit < ApplicationRecord
belongs_to :property, inverse_of: :units
has_many :tenants, inverse_of: :unit
end
Lastly the show#tenants in rake routes is:
tenant GET /tenants/:id(.:format) tenants#show
I have extensively searched for this topic, but haven't had any success. Any help is appreciated. Rails 5.1
The route you are showing near the end of your question:
tenant GET /tenants/:id(.:format) tenants#show
is not the tenants index; it is the individual tenants/show route. You can tell this because it includes :id, which means it will show you a specific tenant having that id.
Try running rake routes again. The index route should look like this:
tenants GET /tenants(.:format) tenants#index
If you want to return to the tenants index after creating or updating a Tenant record, then you need to specify that path in your TenantsController. In both the #create and #update actions, your redirect line (after if #tenant.save and if #tenant.update, respectively) should read:
redirect_to tenants_path
That will take you to the TenantsController, #index action.
In the alternative, if you want to return to the individual tenant show page, then instead change both of those redirects in the TenantsController in both the #create and #update actions to:
redirect_to tenant_path(#tenant)
That will take you to the TenantsController, #show action for the current #tenant.
I have two user types: Artist and Fan. I want Fans to be able to follow Artists. So far following them does not work, but unfollowing does. I have create and destroy set up the same way, but can't seem to get it to work. I get the error Couldn't find Artist without an ID when trying to create a Relationship. Anyway I can find the Artist's ID?
Code below:
relationships_controller.rb
class RelationshipsController < ApplicationController
before_action :authenticate_fan!
def create
#relationship = Relationship.new
#relationship.fan_id = current_fan.id
#relationship.artist_id = Artist.find(params[:id]).id #the error
if #relationship.save
redirect_to (:back)
else
redirect_to root_url
end
end
def destroy
current_fan.unfollow(Artist.find(params[:id]))
redirect_to (:back)
end
end
artists_controller.rb
def show
#artist = Artist.find(params[:id])
end
artists/show.html.erb
<% if fan_signed_in? && current_fan.following?(#artist) %>
<%= button_to "unfollow", relationship_path, method: :delete, class: "submit-button" %>
<% elsif fan_signed_in? %>
<%= form_for(Relationship.new, url: relationships_path) do |f| %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
<% end %>
models/fan.rb
has_many :relationships, dependent: :destroy
has_many :artists, through: :relationships
belongs_to :artist
def following?(artist)
Relationship.exists? fan_id: id, artist_id: artist.id
end
def unfollow(artist)
Relationship.find_by(fan_id: id, artist_id: artist.id).destroy
end
models/artists.rb
has_many :relationships, dependent: :destroy
has_many :fans, through: :relationships
belongs_to :fan
routes.rb
resources :relationships, only: [:create, :destroy]
Basically, you need to send artist_id to the action. Change your form to this. There is a lot of refactoring required but this one will work for you:
<%= form_for(Relationship.new, url: relationships_path) do |f| %>
<%= hidden_field_tag :artist_id, #artist.id %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
In controller, you can access it like:
#relationship.artist_id = Artist.find(params[:artist_id]).id
I would consider solving this with a nested route instead:
resources :artists, shallow: true do
resources :relationships, only: [:create, :destroy]
end
this will create these routes in addition to the regular CRUD routes for artist:
Prefix Verb URI Pattern Controller#Action
artist_relationships POST /artists/:artist_id/relationships(.:format) relationships#create
relationship DELETE /relationships/:id(.:format) relationships#destroy
Notice that we use shallow: true which scopes the create route under /artists/:artist_id but not the destroy route.
You can then change your form:
<%= form_for(Relationship.new, url: artist_relationships_path(artist_id: #artist)) do |f| %>
<%= f.submit "follow", class: "submit-button" %>
<% end %>
And your controller:
class RelationshipsController < ApplicationController
before_action :authenticate_fan!
def create
current_fan.relationships.build(artist: Artist.find(params[:artist_id]))
if #relationship.save
redirect_to(:back)
else
redirect_to root_url # makes more sense to redirect back to #artist ?
end
end
def destroy
#relationship = current_fan.relationships.find(params[:id])
#relationship.destroy
redirect_to(:back)
# or redirect back to the artist page.
# redirect_to #relationship.artist
end
end
Notice how we also refactor the destroy action - You never want to have a route with an :id param which points to a completely different resource. Thats just poor REST design. We don't even need to know the artist ID if we know the id of a relationship. Instead here the ID refers to the Relationship resource.
To create a link to destroy the relationship you would do:
<%= link_to 'Unfollow', relationship_path(#relationship), method: :delete %>
And lets get rid of the Fan#unfollow method.
While we are at it we can fix the Fan#following? method.
def following?(artist)
relationships.exists?(artist: artist)
end
By using the relationship (in the ActiveRecord sense of the word!) instead of querying the Relationship model directly you can use eager loading to avoid additional queries and also you don't have to specify the fan.
How can I best implement this feature: As an admin, I can assign a Resident Manager to a Hall.
I have a User model with a namespace routing for the admin -I intend on having another namespace routing that would hold the functions of the RM-
I have a Hall model.
Since its a many-many relationship between the above to models, I have a Management join model which contains only user_id and hall_id columns.
I know implementing the above feature, entails creating a new record in the management table but I don't know how to do it. I didn't think using a form (management#new) would solve this because the admin should not know the user_ids/hall_ids...
BELOW IS WHAT I HAVE TRIED TO DO BUT I CAN'T GET IT RIGHT
When the admin gets to the user index page, s/he should see a link for Hall Assignment for each user. This link leads to the management show page for that particular user, which would show the list of halls assigned to that user and also the show all the other remaining halls that isn't assigned to the user. So, either clicking an ADD button or on the hall's name should add it to that user's assigned halls list which is on the same page.
Management#show page
<h2><%= #user.email %>'s assigned halls</h2>
<% #user.managements.each do |management| %>
<%= management.hall.name %>
<% end %>
<p> HALL LISTS </p>
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add" %> </li>
<% end %>
</ul>
Here's is my Management controller
class Admin::ManagementsController < ApplicationController
def index
#managements = Management.all
end
def show
#user = User.find(params[:id])
#halls = Hall.all
end
def create
#management = Management.create(managements_params)
redirect_to admin_management_path
end
private
def managements_params
params.
require(:management).
permit(user_id: params[:user_id], hall_id: params[:hall_id])
end
end
And here's a piece of what my routes file looks like:
namespace :admin do
resources :users, only: [:index, :update]
resources :halls, only: [:index, :new, :create]
resources :managements, only: [:index, :new, :create, :show] do
resources :halls, only: [:index]
end
end
Your "add" button is just a mini form (with mostly hidden fields). You can instead just make it an actual form (with the submit-button having the text "Add") and the id-values filled in from the item on the page... it just points to the same routes that you'd normally point the form that you'd find in the new template.
if you want more detail, then show us the code that you have written (rather than a verbal description of it).
Edit:
Ok, so you'd put a button on the page like this
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add", managements_path(management: {user_id: #user.id, hall_id: hall.id}, method: :put ) %> </li>
<% end %>
</ul>
Notice the managements_path - you might need to check that that routing is correct (check it against what is in rake routes). Note that you're passing in the user id and the hall id, and that you must set the method to "put" on the button.
First things first -
How can I implement a 'create' action without 'new' action
It's relatively simple to do - you will need a create action somewhere, but a new action is just a way to build the respective ActiveRecord object for your controller.
If you do this in another action, you just have to make sure you point the form to the correct create action (and that create action to redirect back to
--
New / Create
Here's how you could handle the new / create actions in different controllers, as an example for you:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def index
#hall = Hall.new
end
end
#app/controllers/halls_controller.rb
Class HallsController < ApplicationController
def create
#hall = Hall.new hall_params
redirect_to users_path if #hall.save
end
private
def hall_params
params.require(:hall).(:hall, :attributes, :user_id)
end
end
This will allow you to show the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= form_for #hall, url: hall_path do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.text_field :x %>
<%= f.submit %>
<% end %>
<% end %>
--
Fix
ADD button or on the hall's name should add it to that user's assigned halls list
For this, I don't think you'd need a create action in the "traditional" sense - it will be more about adding new halls to a user's current halls. This is much different than creating a new hall itself:
#config/routes.rb
namespace :admin do
resources :users do
post "hall/:id", to: :add_all #=> domain.com/admin/users/:user_id/hall/:id
end
end
#app/controllers/admin/users_controller.rb
Class UsersController < ApplicationController
def add_hall
#user = User.find params[:user_id]
#hall = Hall.find params[:id]
#user.halls << #hall
end
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_halls
has_many :halls, through: :user_halls
end
#app/models/hall.rb
Class Hall < ActiveRecord::Base
has_many :user_halls
has_many :users, through: :user_halls
end
#app/models/user_hall.rb
Class UserHall < ActiveRecord::Base
belongs_to :user
belongs_to :hall
end
This uses the ActiveRecord collection methods to make this work, to which you'll be able to provide the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= button_to "Add Hall Test", user_add_hall_path(user, 1) %>
<% end %>
I have multiple social networks available in my model:
class Social < ActiveRecord::Base
enum kind: [ :twitter, :google_plus, :facebook, :linked_in, :skype, :yahoo ]
belongs_to :sociable, polymorphic: true
validates_presence_of :kind
validates_presence_of :username
end
I want to declare manually the kinds used. Maybe I need to have an alternative to fields_for?
<%= f.fields_for :socials do |a| %>
<%= a.hidden_field :kind, {value: :facebook} %> Facebook ID: <%= a.text_field :username, placeholder: "kind" %>
<%= a.hidden_field :kind, {value: :twitter} %> Twitter ID: <%= a.text_field :username, placeholder: "kind" %>
<%= a.hidden_field :kind, {value: :google_plus} %> Google ID: <%= a.text_field :username, placeholder: "kind" %>
<%= a.hidden_field :kind, {value: :linked_in} %> Linked In ID: <%= a.text_field :username, placeholder: "kind" %>
<% end %>
But I get just one value saved and displayed for all four IDs.
When doing a fields_for on each individual item I get repeated kinds and repeated values
NOTE: There should be only one of each kind associated with this profile form.
I believe that I need to use something like find_or_create_by to ensure only one of each kind is made and loaded in the editor as the fields_for simply loads everything in the order they were saved. Maybe showing how this Rails find_or_create by more than one attribute? could be used with just kind.
I need to ensure that product will only save one of each kind and when you edit it; it will load correctly by kind and not just any belonging to.
Since in my example all four will display what was saved in the first field on the edit page it's clear it's not ensuring the kind at the moment.
I'd like to use something like this in my application_controller.rb
def one_by_kind(obj, kind)
obj.where(:kind => kind).first_or_create
end
How would I substitute the fields_for method with this?
There are some problems here:
1 - how will you define a Social having many types or kinds when you can only pick one enum state for it ?
2 - Definitely you cannot use :username as the name for all the fields in your model. Rails will understand only the last one as the valid one. All others will be overriden.
But you can solve this problem simplifying your tactics:
Forget about setting kind in your form as a hidden field, that really won't work the way you want.
1 - Instead of a product having many socials, product has_one social, which keeps all data related to the social networks for that model.
class Product < ActiveRecord::Base
has_one :social
accepts_nested_attributes_for :social
#...
end
2 - Your form will be much simpler and you decide the order of appearance. Also you can reuse it on the edit view as a partial:
#your form header with whatever you need here...
<%= f.text_field(:name) %>
<%= f.fields_for :social do |a| %>
Facebook ID: <%= a.text_field :facebook_username %>
Yahoo ID: <%= a.text_field :yahoo_username %>
Linkedin ID: <%= a.text_field :linkedin_username %>
Twitter ID: <%= a.text_field :twitter_username %>
<% end %>
3 - In your new method, you'll need to initialize the has_one relationship:
def new
#product = Product.new
#product.build_social
end
4 - If you're using Rails 4, don't forget to whitelist the allowed attributes:
def product_params
params.require(:product).permit([:name, socials_attributes: [:twitter_username,
:facebook_username, :linkedin_username, :google_username, :skype_username, :yahoo_username] ])
end
5 - Then in your controller, you can assign many kinds to your model based on the fields that were filled. Use a before_save callback in your model for that. Something checking your fields like
def assign_social_kinds
if !self.twitter_username.blank? #or some more refined validation of it
self.twitter = true
end
if !self.skype_username.blank?
self.skype = true
end
end
Manual Polymorphic Creation in Rails
Alright I've discovered the solution. Here's what I've got.
models/profile.rb
class Profile < ActiveRecord::Base
has_many :socials, as: :sociable, dependent: :destroy
accepts_nested_attributes_for :socials, allow_destroy: true
end
models/social.rb
class Social < ActiveRecord::Base
enum kind: [ :twitter, :google_plus, :facebook, :linked_in, :skype, :yahoo ]
belongs_to :sociable, polymorphic: true
validates_presence_of :kind
validates_presence_of :username
end
controllers/profiles_controller.rb
class ProfilesController < ApplicationController
before_action :set_profile, only: [:show, :edit, :update, :destroy]
before_action :set_social_list, only: [:new, :edit]
def new
#profile = Profile.new
end
def edit
end
private
def set_profile
#profile = Profile.find(params[:id])
end
def set_social_list
#social_list = [
["linkedin.com/pub/", :linked_in],
["facebook.com/", :facebook],
["twitter.com/", :twitter],
["google.com/", :google_plus]
]
end
def profile_params
params.require(:profile).permit(
:socials_attributes => [:id,:kind,:username,:_destroy]
)
end
end
I've shortened the actual file for just what's relevant here. You will need any other parameters permitted for your use case. The rest can remain untouched.
controllers/application_controller.rb
class ApplicationController < ActionController::Base
def one_by_kind(obj, kind)
obj.where(:kind => kind).first || obj.where(:kind => kind).build
end
helper_method :one_by_kind
end
This is where the magic will happen. It's designed after .where(...).first_or_create but uses build instead so we don't have to declare build for the socials object in the profile_controller.
And lastly the all important view:
(polymorphics most undocumented aspect.)
views/profiles/_form.html
<% #social_list.each do |label, entry| %>
<%= f.fields_for :socials, one_by_kind(#profile.socials, #profile.socials.kinds[entry]) do |a| %>
<%= a.hidden_field :kind, {value: entry} %><%= label %>: <%= a.text_field :username %>
<% end %>
<% end %>
The #social_list is defined in the profile_controller and is an array of label & kind pairs. So as each one gets passed through, the one_by_kind method we defined in the application_controller seeks for the first polymorphic child that has the right kind which we've named entry. If the database record isn't found, it is then built. one_by_kind then hands back the object for us to write/update.
This maintains one view for both creation and updating polymorphic children. So it allows for a one of each kind within your profile and social relation.
I want to add the ability of a user to have several pictures associated with his / her user account.
I have the following classes:
class User < ActiveRecord::Base
has_many :assets
accepts_nested_attributes_for :assets
end
class Asset < ActiveRecord::Base
belongs_to :assetable, :polymorphic => true
belongs_to :user
end
I want to have a screen that just has the upload image functionality:
def add_profile_picture
#user=User.find(params[:id])
1.times {#user.assets.build}
end
form:
<%= form_for #user do |u| %>
<%= u.fields_for :assets do |asset| %>
<%= asset.file_field :asset %>
<%= asset.text_field :description %><br />
<% end %>
<%=u.submit %>
<% end %>
When I submit, it looks like the id value goes in ok in development.log:
"id"=>"1"
but I get the error:
undefined method `update_attributes' for nil:NilClass
Since I just have the asset fields, is there anything special I need to do? Also, because the belongs_to :user exists, could that be causing problems?
Basically:
asset:
user_id:
assetable_type:
assetable_id:
Any help would be appreciated. Don't do much Rails forms stuff.
thx
edit #1
class UsersController < ApplicationController
def add_profile_picture
#user=User.find(params[:id])
1.times {#user.assets.build}
end
thx
Okay - there are a few problems with your code here. I would highly recommend you read both the Action Controller Overview and the Rails Routing guides to get some more information about this.
In any case, you're getting the error because the form you have there will be trying to use the users#update action in the UsersController.
You've got a couple options. One is to create the necessary routes for the custom action, or you can create a nested resource, and make a form for adding the asset.
In this case, you'd do something like this:
in routes.rb
resources :users do
resources :assets, :only => [:new, :create] # Or any other actions you might want. It's best practise to limit these.
end
Then, in the AssetsController, you can do something similar to this:
def new
#asset = Asset.new
end
def create
#asset = Asset.new(params[:asset])
#asset.user_id = params[:user_id] if params[:user_id]
#asset.save!
end
and your form will look something like this:
<%= form_for #asset do |f| %>
<%= f.file_field :asset %>
<%= f.text_field :description %><br />
<%=f.submit %>
<% end %>