The following form in a partial called locationpicker:
<%= simple_form_for(current_user, url: registration_path(current_user), method: :put ) do |f| %>
<% f.association :location, collection: Location.where(category: 'Country'), label: false, input_html: {onchange: "this.form.submit()"} %>
<% end %>
brings up this error:
Could not find a valid mapping for #<User id:...
It's the registration_path(current_user) which is causing it. My suspicions were that something has changed in the routes or the users model, but I can't for the life of me think what or know where to start looking. I thought it might be the recent inclusion of ActiveModel::Dirty but removing that doesn't solve the problem.
Routes.rb:
scope ":locale", locale: /#{I18n.available_locales.join("|")}/ do
devise_for :users, :controllers => {:registrations => "registrations"}
...
end
User.rb
class User < ActiveRecord::Base
include ActiveModel::Dirty
devise :database_authenticatable, :registerable, #:encryptable,
:recoverable, :rememberable, :trackable, :validatable, :lockable
has_one :assignment
has_one :role, :through => :assignment
has_many :changerequests, inverse_of: :created_by, foreign_key: "created_by_id"
belongs_to :location, required: true
belongs_to :person, inverse_of: :user, required: true
has_many :people_details, through: :person
scope :inclusive, -> {includes(:person).includes(:people_details).includes(:location).includes(:assignment).includes(:role)}
after_update :update_locale
after_save :expire_caches
def role?(role_sym)
role_name.to_sym == role_sym
end
def role_group?(role_sym)
role_group_name.to_sym == role_sym
end
def send_on_create_confirmation_instructions
true
end
def update_locale
if locale_changed?
I18n.locale = self.locale || "en"
self.expire_caches
end
end
def country
if self.location.category == "Country"
self.location
elsif self.location.ancestors.where(category: "Country")
self.location.ancestors.where(category: "Country").first
elsif self.location.children.where(category: "Country")
self.location.children.where(category: "Country").first
end
end
def expire_caches
#Admin users can change their location so the caches need expiring
if location_id_changed? or locale_changed?
ctrlr = ActionController::Base.new
#Clear fragments
ctrlr.expire_fragment("#{id}_#{locale}_location_picker")
etc...
end
if location_id_changed?
#Clear other caches
Rails.cache.delete("#{id}_locations_scope")
.... etc
end
end
Try adding as: in your simple_form_for call:
<%= simple_form_for(current_user, as: :user, url: registration_path(current_user), method: :put ) do |f| %>
<% f.association :location, collection: Location.where(category: 'Country'), label: false, input_html: {onchange: "this.form.submit()"} %>
<% end %>
Normally, the devise registrations edit form looks like:
= simple_form_for(resource, as: resource_name, url: user_registration_path, html: { method: :patch }) do |f|
In other words, using "resource" not "current_user". You might try that as well, if your form partial is being rendered within the devise controller context.
Related
1. Description core problem
I am implementing the devices_invitable gem for inviting users. The gem is correctly added, as I am able to send users the standard devices_invitable email to invite them for the application.
The problem that I am encountering, is that I am not sure how to enable an admin (to be identified with enum) to assign the role of the user they are inviting (e.g. they should enter an email AND role of the user they invite, which should then be correctly added to my DB).
Note: As you can see below, I'm currently trying to solve it using nested forms with the cocoon gem, but I am open to other approaches.
2. My current application
The application has 2 tables with a many to-many relationship between them:
(i) Users, with role attributes defined by enum.
(ii) Parks
The idea is to let an admin invite a new user to a park (not entire application) by entering email+role.
3. The code
user.rb
class User < ApplicationRecord
has_many :user_parks, dependent: :destroy
has_many :parks, :through => :user_parks
enum role: [:owner, :admin, :employee, :accountant]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
park.rb
class Park < ApplicationRecord
has_many :user_parks, dependent: :destroy
has_many :users, :through => :user_parks
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
parks_controller.rb
class ParksController < ApplicationController
def update
#park = Park.find(params[:id])
authorize #park
url = Rails.application.routes.recognize_path(request.referrer)
last_action = url[:action]
if last_action == 'edit_users' & #park.user.last.nil?
#user=#park.user.last.invite!(email: park_params[:user][:email])
end
#park = #park.update_attributes(park_params)
redirect_to parks_path
end
def edit_users
#park = Park.find(params[:id])
authorize #park
end
private
def park_params
params.require(:park).permit(:name, users_attributes: [:email, :role, :_destroy])
end
end
edit_users.html.erb
<%= render 'users_edit_form', park: #park%>
_users_edit_form
<%= simple_form_for [#park] do |f|%>
<h1>Park</h1>
<% #park.users.each do |user| %>
<%= user.email %>
<% end %>
<div>
<% f.simple_fields_for User.new, :url=> new_user_invitation_path, html: { class: 'form-inline' } do |user| %>
<%= user.input :email %>
<%= user.input :role, priority: [:employee], collection:[:owner, :admin, :employee, :accountant] %>
<%= f.button :submit, 'Invite User', class: 'btn-primary' %>
<% end %>
</div>
#TEST for standard view (sends invite but does not assign park/role)
<%# link_to "new user add", new_user_invitation_path %>
<%= f.button :submit, 'Invite User', class: 'btn-primary' %>
<% end %>
1st issue I can see is you are using a bitwise & on this line:
if last_action == 'edit_users' & #park.user.last.nil?
you would usually use a logical and && or and
2nd issue with that line is #park.user should be #park.users if it's declared as a has_many
3rd issue is I'm not sure you need to be looking at request.referrer?
Assuming you can get to the invite! line then you might want to check the docs for the 2 different versions of invite!
When the user is already created:
user = User.find(42)
user.invite!(current_user) # current user is optional to set the invited_by attribute
see example in README
OR
When the user isn't created yet:
User.invite!(email: 'new_user#example.com', name: 'John Doe')
# => an invitation email will be sent to new_user#example.com
see example in README
I'm trying to bring a list of Users that a "request" has been accepted for a user to select from. I set up the collection and what is showing up on the view page is either the user id when I put :user_id or its shows <User:0x007fbdrf252a60> when i put in user. I tried putting in :name but it returns a "undefined method name" error.
Currently my form looks like:
<%= form_for :event_logs, url: event_logs_path do |f| %>
<%= f.select :user_id, options_from_collection_for_select(#user_event_log, :id, :name ) %>
<%= f.hidden_field :event_id, :value => #event.id %>
<%= f.submit 'Send', class: 'btn btn-success' %>
<% end %>
And in the controller for the show its:
#user_event_log = RequestLog.usr_req_by current_user
The user_req_by in the model is: scope :usr_req_by, ->(user) { where(user_id: user.id) }
If more information is needed I'm more than happy to add it. Thank you
User Model:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates_presence_of :name
has_many :request_logs, dependent: :destroy
acts_as_messageable
def first_name
self.name.split.first
end
def last_name
self.name.split.last
end
end
Request Log Model:
class RequestLog < ApplicationRecord
enum status: { accepted: 0, rejected: 1 }
belongs_to :user, optional: true
belongs_to :request, optional: true
validates :user_id, presence: true
validates :request_id, presence: true
scope :usr_req_by, ->(user) { where(user_id: user.id) }
scope :req_itp, ->(requests) { where(request_id: requests) }
end
The collection iterated over, is a collection of RequestLog. So when you use :name in options_from_collection_for_select, it tries to call .name on a RequestLog.
I suppose you want the users name there? If so, you can add a helper method to RequestLog
def user_name
return user.name
end
and use :user_name in the view.
Another option, is to pass an object, that responds to .call as a param, as so:
<%= form_for :event_logs, url: event_logs_path do |f| %>
<%= f.select :user_id, options_from_collection_for_select(#user_event_log, :id, ->(log){ log.user.name }) %>
<%= f.hidden_field :event_id, :value => #event.id %>
<%= f.submit 'Send', class: 'btn btn-success' %>
<% end %>
I haven't tried this out, but I did look at the source for options_from_collection_for_select, that uses value_for_collection under the hood; Defined as
def value_for_collection(item, value)
value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value)
end
(See Rails Source Code and apidock.com)
I'm working on build a Twitter-like sample app, but I am stuck at creating a following method. I have created a Relationship model and the necessary classes to accomplish the task, but when I load the page, I receive the error "NoMethodError" on this line: <%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
I render the form on the profile page like so:
<%= render '/components/follow_button', :user => User.find_by_username(params[:id]) %>
Here is the form:
<% if current_user.id != user.id %>
<div class="col s12">
<div class="panel panel-default">
<% if !current_user.following?(user) %>
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
<div><%= hidden_field_tag :followed_id, user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
<% else %>
<%= form_for(current_user.active_relationships.find_by(followed_id: user.id),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
<% end %>
</div>
</div>
<% end %>
User.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates_uniqueness_of :email, :case_sensitive => false
validates_uniqueness_of :username, :case_sensitive => false
has_many :posts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
def follow(other)
active_relationships.create(followed_id: other.id)
end
def unfollow(other)
active_relationships.find_by(followed_id: other.id).destroy
end
def following?(other)
following.include?(other)
end
end
Relationship.rb
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
RelationshipsController:
class RelationshipsController < ApplicationController
def create
user = User.find(params[:followed_id])
current_user.follow(user)
redirect_to(:back)
end
def destroy
user = Relationship.find(params[:id]).followed
current_user.unfollow(user)
redirect_to(:back)
end
end
Any help would be greatly appreciated.
EDIT: Following method used in Routes.rb
devise_for :users
resources :users do
member do
get :following, :followers
end
end
On the form_for documentation, it eventually talks about 'Resource-oriented style' forms
In the examples just shown, although not indicated explicitly, we still need to use the :url option in order to specify where the form is going to be sent. However, further simplification is possible if the record passed to form_for is a resource, i.e. it corresponds to a set of RESTful routes, e.g. defined using the resources method in config/routes.rb. In this case Rails will simply infer the appropriate URL from the record itself
<%= form_for(Post.new) do |f| %>
...
<% end %>
is equivalent to something like:
<%= form_for #post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>
...
<% end %>
Looking at your code, since you are not passing a :url option to form_for and you are passing an instance of a model, it's assuming your model was configured in the routes file with resources :relationships, which generates some named route helpers such as relationships_path, the method that it's complaining is missing.
To fix your problem, you need to pass form_for a :url where your controller lives and where it should post to, or update your routes to use the resources :relationships. You can read more information about the resources routing here. If you add
resources :relationships, only: [:create, :destroy]
outside of your devise_for block, you'll end up with 2 new routes for
Prefix Verb URI Pattern Controller#Action
relationships POST /relationships(.:format) relationships#create
relationship DELETE /relationships/:id(.:format) relationships#destroy
and the named helpers relationships_path and relationship_path which your 2 form_for tags are going to be looking for.
Don't forget to restart your server after making changes to your config/routes.rb file to make sure rails picks them up.
I have got the following rails app, which allows users to subscribe to widgets. i.e. Many-to-Many through model with Users-to-Widgets through Subscriptions.
My Models:
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :widget
validates_uniqueness_of :user_id, scope: :widget_id
end
class User < ActiveRecord::Base
has_many :subscriptions
has_many :widgets, through: :subscriptions
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class Widget < ActiveRecord::Base
has_many :subscriptions
has_many :users, through: :subscriptions
end
My Controller:
class SubscriptionsController
def edit
#user = current_user
end
def update
#user = current_user
if #user.update(user_subscription_params)
redirect_to #user, notice: "Subscriptions updated"
else
render :edit
end
end
private
def user_subscription_params
params.require(:user).permit(:widget_ids)
end
end
and this is being rendered in views/subscriptions/_forms.html.erb like this:
<%= form_for #user, url: user_subscription_path, method: :patch do |f| %>
<%= f.collection_check_boxes :widget_ids, Widget.all, :id, :name %>
<%= f.submit %>
<% end %>
I am getting the error:
No route matches {:action=>"show", :controller=>"subscriptions"} missing required keys: [:id]
I would be really grateful of any ideas how to fix this.
Edit:
Routes.rb:
Rails.application.routes.draw do
root 'home#index'
resources :widgets
resources :subscriptions
devise_for :users
devise_scope :user do
get '/users/sign_out' => 'devise/sessions#destroy'
end
authenticated :user do
root to: 'home#index', as: :authenticated_root
end
You can try
<%= form_for #user, url: user_subscription_path(#user), method: :patch do |f| %>
<%= f.collection_check_boxes :widget_ids, Widget.all, :id, :name %>
<%= f.submit %>
<% end %>
Not sure why this isn't working but I've been mis-guided I think....I'd rather not re-route but simply have the photo uploaded in the current landing_welcome page.. not be transfered to an update template.
error:
Couldn't find Photo with id=16 [WHERE "photos"."attachable_id" = $1 AND "photos"."attachable_type" = $2]
def update
#user = current_user.photos.find(params[:id])
#user.update_attributes!(person_params)
redirect_to #user
end
Users_controller.rb
class UsersController < ApplicationController
def update
#user = current_user.photos.find(params[:id])
#user.update_attributes!
redirect_to #user
end
def show
end
end
landing_welcome.html.erb
<div class="tab-pane" id="tab2">
<%= nested_form_for current_user, :html=>{:multipart => true } do |f| %>
<%= f.fields_for :photos do |p| %>
<p>
<%= image_tag(p.object.file.url) if p.object.file? %>
<%= p.label :file %><br />
<%= p.file_field :file %>
</p>
<%= p.link_to_remove "Remove this attachment" %>
<% end %>
<%= f.link_to_add "Add photos to your profile", :photos %>
<br /><br />
<p><%= f.submit %></p>
<% end %>
</div>
routes.rb
root to: "home#landing"
devise_for :users, :controllers => {:registrations => "users/registrations",
:sessions => "users/sessions",
:passwords => "users/passwords"}
get "welcome", to: "home#landing_welcome"
devise_scope :user do
# get "edit/edit_account", :to => "devise/registrations#edit_account", :as => "account_registration"
get 'edit/edit_account' => 'users/registrations#account_registration', as: :edit_account
end
patch '/users/:id', to: 'users#update', as: 'user'
photo.rb
class Photo < ActiveRecord::Base
attr_accessible :file
belongs_to :attachable, :polymorphic => true
mount_uploader :file, FileUploader
end
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email,
:password,
:password_confirmation,
:zip,
:gender,
:remember_me,
:first_name,
:last_name,
:birthday,
:current_password,
:occupation,
:address,
:interests,
:aboutme,
:profile_image,
:photos_attributes
has_many :photos, as: :attachable
accepts_nested_attributes_for :photos
mount_uploader :profile_image, ProfileImageUploader
validates :gender, :presence => true
def number_of_users
User.all.count
end
end
For lack of a better answer, I think your woes lie in the query generated by your app:
Couldn't find Photo with id=16 [WHERE "photos"."attachable_id" = $1 AND "photos"."attachable_type" = $2]
Two factors are present here:
Why is your attachable_id being called as $1?
Why is your attachable_type a number, not a string?
Polymorphic Association
Your query is trying to load a Photo with ID=16, however, your query is also trying to validate the model, to satisfy the polymorphic association. This is where the error is coming from
As you've not stated which route / page this error is showing, I can only speculate as to the cause of the problem:
#user = current_user.photos.find(params[:id])
This query is really bad for a number of reasons:
You're using the current_user object directly. I might be wrong here, but this is used by Devise / Warden to store relative information about the logged-in user, and is not a real ActiveRecord object (with relational data etc)
You're trying to .find on top of a relation (current_user.photos)
Although this might be incorrect, I would look at doing this for that query:
#photo = User.joins(:photos).find(current_user.id).where("photos.id = ?", params[:id])
Then you can perform the updates you require