Rails has_one relationship safe resource.build_resource in controller - ruby-on-rails

I have a page that takes a user through a short sign up tutorial when they create their account in order to create their first resource. In my app, :hotel belongs to :user, and :user has_one hotel. For the tutorial page, in my controller, I have:
#hotel = current_user.build_hotel
Which works, except that it a user somehow finds him back on the tutorial page that command disassociates their previously created hotel. In other words, the second time the user accesses the page with:
#hotel = current_user.build_hotel
The user_id field in the hotel they created the first time becomes nil. Obviously that is a serious problem. I can do a before_filter on that page, but I'm not very happy about having a way for the user to screw everything up simply by visiting a page. How should I properly use the build command for a has_one relationship?

You can test for the existence of a hotel before building it:
#hotel = current_user.hotel || current_user.build_hotel

Related

Rails 5 - Best way to hold onto params for creating associations?

Here's the scenario to illustrate my question. I have 2 models:
# models/post.rb
belongs_to :user
validates_presence_of :comment
And we have a devise model called Users
# models/user.rb
has_many :posts
What I would like to achieve:
Person comes to the website, is able to create a Post, after creating the Post, they are prompted to create an account. After creating the account, the Post that they just created would be associated to the User they just created.
Usually i'd make use of routes to hold the params[:id] which can be accessed in the controller method. For example the URL may look something like this:
www.foo.com/foo/new/1
And then I can do this:
# foo_controller.rb
def new
#foo = Foo.new
#parent = Parent.find(params[:id])
end
And in the view I can simply access #parent and use a hidden field to fill the parent ID.
But when routing through so many different pages (such as creating a Devise User), how do I hold onto the parent/child ID such that I can still create that association?
Using an hidden field or the route to store the id, with no authorization in the process, would not be secure. What if I just use the browser inspector and change the value of the id ? Your cool post would be mine.
What you could do is, for instance, add a field called guest_id to the Post, in which the value is unique (like SecureRandom.uuid), and also store that value in the session.
Thus, after the user is created, you could do something like that
if (post = Post.find_by(guest_id: session[:guest_id])).present?
post.update(user_id: current_user.id)
end

Create User Account Settings Page in Ruby on Rails with devise

I am new to Ruby on Rails and I have created a project that contains a User table (generated by devise) and a AccountSetting table that contains user specific account settings (this table has a foreign key that relates to the id in the User table thus each User has zero or one AccountSettings). I have my seed data working fine, and I can seed the database with users that have user specific account settings. The User table is related to the AccountSetting table with a "has_one :accountsetting" and the AccountSettings table "belongs_to :user". This all works and makes sense. However, I have a method called "show_user_setting" in my UserSettings controller, and I do not know how to ONLY SHOW the account settings for that specific authenticated user.
So, how can I only display the user setting for the currently logged in user? Again, I am using devise.
My general idea of how to do this would be something like this. However I know this is incorrect, but for the purpose of an explanation, here it is.
def show_user_setting
#setting = AccountSetting.find(current_user)
end
My idea is that the #setting will contain the setting for the currently logged in user. Thanks in advance!
You should do this:
#app/models/account_setting.rb
class AccountSetting < ActiveRecord::Base
belongs_to :user
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :account_setting
end
This will allow you to call the following:
#setting = current_user.account_setting
Our Setup
For what it's worth, we do something similar:
#app/models/user.rb
class User < ActiveRecord::Base
before_create :build_profile #-> builds a blank profile on user create
has_one :profile
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
This allows us to put all sorts of different options inside the profile model (we have homepage etc):
The important thing to note here is that the above allows you to delegate various methods to the profile model, allowing you to call the following:
current_user.profile_name
current_user.profile_signin_redirect?
current_user.profile_avatar
etc
Have you tried
def show_user_setting
#setting = AccountSetting.find_by(user_id: current_user.id)
end
The way .find() works is it searches the model for the id passed. So, the way you currently have it is your going to try to search for the id of the model, when you want to find the foreign key. So use Model.find_by(column_name: param). You'll what to change user_id: to the column name of what you're storing the foreign key in, I'm just assuming it's something similar to that.
I'm guessing the show_user_setting function is part of a controller, if it is on a model then read this: accessing devise current_user within model
to set the #setting variable you should be able to do this
#setting = AccountSetting.find(user_id: current_user.id)
or
#setting = AccountSetting.find(user: current_user)

how can I create a form for User model which can be updated after successfully logged into application

Requirement: I need to create an application where user can logged in and can change the background color of that page, can change the font of the page and it should persist with session. so I am trying to create form which accept those attributes from user and can save in database. I am using devise for authentication.
How can I create form for user who is successfully logged into application(authentication is done by devise gem, so user table is already existing) and upon submission of form those attributes should get updated in user table. Any suggestion will be appreciated.
Q1 .What should be the name of controller and view for this form ?
Q2. How the routes can be define.
Q3. Whether controller should have update action to update user table with the extra attributes present in the form.
Thanks. Please comment below if I missed some information needed to provide. You can suggest me if you think this can be achieve in easier way also.
Whatever you want. Sounds li?ke you are just updated user attributes, so i would just use the User#update action
resources :users, only: [:update, :edit] #and any other user routes
? see 1
Also you will want to make sure that people can only edit their own account. In a before action you will want to add.
redirect_to root_path unless current_user == user_you_are_editing
What I would do:
Create a Model called UserPreferences that belongs_to :user, give this Model the attributes 'background_color', etc...
Add statement to user has_one :user_preferences
Create a form for the prefs like any Rails Model, that can only be accessed by current_user.
Use current_user.user_preferences to refer to these values, you can enhance this by placing alias methods in User, for example:
class User < ActiveRecord::Base
def background_color
self.user_preferences.background_color
end
end

Can't find where devise create method to create user

Updated
I want to add more details to this. I have a user model and this user model
has_many :trial_subscriptions
attr_accessible :trial_subscriptions_attributes
accepts_nested_attributes_for :trial_subscriptions, :allow_destroy => true
I am building a custom form so when a user enters a trial_email, the form will create a new user with an associated trial_subscription
The trial_subscription.rb model inherits from manual_subscription model and manual_subscription inherits from subscription model.
The subscription model
belongs_to :user
I need to build the associations from the has_many side. Now I am having trouble figuring out where to post the form (getting the #user to be editted) when devise is involve. I need to know where the create method is when the user gets created and hence my question below.
This will be a separate page from normal sign up in a /sales url so I can not add nested fields to the signup form.
The rails application that I am working on has devise installed on the user model.
In the routes
I have
devise_for :users, :controllers => {:registrations => "registrations"}
resources :users
the sign up form is in views/devise/registrations/new.html.erb
I have a users_controller.rb with a new and create method
in the users_controller.rb there is this before_filter
before_filter :authenticate_admin!
We have active admin installed. I am still quite new to the code. Since this filter exist I am sure the user is not getting created in this controller.
when I go to
/users/sign_up - there is the sign up page
if I do
/users/new - I am redirected to /users/sign_up
so I am guessing that the user is getting created in the registrations controller but the new method is
this
def new
session[:qbid] = params[:qbid]
session[:trial] = params[:trial]
session[:sublength] = params[:sublength]
if session[:trial] = true
#trial_flow = true
end
super
end
there is no User.new object getting passed to the view to get edited? I am guessing that devise doesn't need it.
I am getting confused on where the user is getting created because my task is to create a custom form which creates a new user.
I don't know where to post the form to? the registrations_controller?
The user has a has_many subscriptions association and I need to post a form that both create the user and its subscriptions.
You need to customize devise's registrations controller. See it's doc
Though the doc shows only how to customize the session controller (login), the steps are similar, you just need name your customized controller differently.
If you are wondering, you can see here how devises' controllers look.
When you are customizing those, you are going to use inheritance. That is another topic you should read about.

Maintain associations with act_as_paranoid

I have two models one of them is a User, and another Comments. Comments belong to User.
class User < ActiveRecord::Base
act_as_paranoid
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
When I do user.delete in my controller I get the expected result of the deleted_at column being set and the record being hidden.
My problem is the Comment associations for the user are set to null. So now on the site it shows no user owning the comment. I'd like the comment to still show the user's name not be "None" or "Anonymous" etc.
Looking at the source on github https://github.com/goncalossilva/rails3_acts_as_paranoid/blob/rails3.2/lib/acts_as_paranoid/core.rb it seems to call run_callbacks which in turn results in Rails 3 falling back to Nullify default for associations.
In my case I just want the user account to be closed off when deleted. Not showing up in queries anymore so that Authlogic will deny them and the User index page won't show them. But still allowing everything a user owns to still be owned by them (since they may come back, etc.).
Is there a better way to do this then acts_as_paranoid?
Rather then go to the trouble of overriding the destroy method I created a close method that simply sets closed_at to a timestamp. If you set default scope to something like:
default_scope { where("closed_at IS NULL") }
Then the model won't show up to any queries including User.All. You can delete the scope to get a full query essentially I took these ideas from act_as_paranoid but much more simplified. The problem is that then even though the Comments still have user_id set, the default scope runs with any association load. So say
c = Comment.first
c.user
That will output nil if user_id is a closed account. In my case the easiest solusion was to remove default scoping and modify my Authlogic function to:
def self.find_by_username_or_email(login)
u = User.find(:first, :conditions => ["lower(username) = ?", login.downcase]) || User.find_by_email(login)
return u unless u.closed_at
end
This way closed accounts can't login. Anywhere I list out users in my views I used a hide_closed scope.
Not sure if this was the best most elegant solution. But for my purposes it works.

Resources