Rails validation to make something not editable if live - ruby-on-rails

I'm trying to get my head around rails validation. So far i have this
def live
person.live
end
However i have no idea what to do after this, The users have a button to click to make there person live. Which changes a value in the DB from false to true.
they then have a link to be able to edit the user which is here
/people/{{person.id}}/edit
How do i make it so the users cannot access the link (via typing it into the browser) after the event is live?
Thanks

I would recommend using some kind of authorization gem. Personally i use CanCanCan.
There you can specifiy what a user can access and not and where it should redirect to etc.
In Ability.rb specify this
can :update, Person, :live => false
In Controllers use this
authorize! :edit, #person
In views you can then use something like this
<% if can? :update, #person%>
<%= link_to "Edit", edit_person_path(#person) %>
<% end %>

"How do i make it so the users cannot access the link (via typing it into the browser) after the event is live?"
You can use before_action filter to check if live field is true. Something like:
Controller:
before_action :check_if_live_true
private
def check_if_live_true
if live_field.true?
redirect_to some_path
end
end
Which will redirect if the live field is true, then someone enters the matching URL.

Related

Rails require user's profile setup after sign_up

How can I require user to create profile after he has signed up with Devise?
I am thinking about using Wicked Wizard gem, but it is possible for user to skip all steps and just access the website.
User must setup their profile first, only then they should have access to website.
I am thinking about this implementation:
SomeController.rb
before_action :authenticate_user!
before_action :check_if_profile_created?
private
def check_if_profile_created?
current_user.profile
end
end
But this solution will do this check on all requests made by user, which I think is not good. Is there any other ways to do this? Or how can it be implemented with Wicked Wizard gem? I haven't found how to make it redirect from all pages to current step in wizard.
I have a solution that I just implemented two days ago. There is no much documentation on the Internet about this matter. There might be many ways to do this. This is just the way I did it. So, without more delay, let's get to it.
The Concept:
The whole idea, at least how I conceive it, is to "force" the recently signed up user, after he/she also confirms its email, to additionally confirm his profile. As simple as that. That's because in my case many of the profile fields are obviously not mandatory, except the first_name, last_name, email and obviously the password. The rest (job_title, company, affiliation, etc, etc, etc), are not mandatory. But the thing is that at the same time, I would like to enforce that every single User has a profile as complete as possible. This is absolutely necessary for purposes that are not necessary to mention here.
Note:
This is based in that you already have a SettingsController, that allows to any logged user to update his own profile and to update his password as well (in a separated action method).
Step # 1: Add the related boolean field
Modify your users table by adding a boolean field.
class AddProfileWasConfirmedToUsersTable < ActiveRecord::Migration[6.1]
def change
add_column :users, :profile_was_confirmed, :boolean, null: false, default: false
end
end
The boolean field profile_was_confirmed will ensure that the User's profile has been confirmed. It's false by default, right from creation, and it will be updated once and only once to true, when the user confirms his profile. The system won't enforce a 100% complete profile after that, because in order to do such thing it would have to be mandatory on the User model, and that would mess up with the initial sign up process (using the usual devise).
Step # 2: Add the necessary routes.
This one is for the view where we will include a form (inn our case reuse the settings profile form):
get :confirm_profile, to: 'settings#confirm_profile'
And this one is for the updating of the incoming fields from the previously mentioned form:
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
So at the end you would have something like this (just an example):
get :confirm_profile, to: 'settings#confirm_profile'
resource :settings, only: [:edit, :update] do
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
patch :change_password, to: "settings#change_password"
resource :site_terms, only: [:edit, :update]
end
Note:
Remember to place the additional get route before the resources declaration. Otherwise the framework will confuse your route with the show action method.
Step # 3: Modify your ApplicationsController
before_action :check_profile_was_confirmed
.
.
.
def check_profile_was_confirmed
return unless current_user.present?
unless current_user.profile_was_confirmed? || devise_controller?
redirect_to confirm_profile_path
end
end
Step # 3: Modify your SettingsController
skip_before_action :check_profile_was_confirmed
def confirm_profile
if current_user.profile_was_confirmed?
flash["notice"] = "Your profile has been already confirmed"
redirect_to dashboard_path
end
end
def update_confirmed_profile
if #user.update(update_params) && confirm_params_ready?
#user.update({ profile_was_confirmed: true })
flash["notice"] = "Profile confirmed"
redirect_to dashboard_path
else
flash["alert"] = "All the fields must be filled"
redirect_to confirm_profile_path
end
end
private
def confirm_params_ready?
params_base = params.require(:user).permit(:first_name, :last_name, :email, :company, :job_title)
params_base.values.all?(&:present?)
end
Step 4: Create your view with the form or reuse the same one you are using for updating the profile.
File:
app/views/settings/confirm_profile.html.erb
<% content_for(:title) { " | Profile Confirmation" } %>
<% content_for(:view_header) do %>
<%= render 'shared/view_header', view_title: 'Profile Confirmation' %>
<% end %>
<%#= render partial: 'nav' %>
<div class="w-prose mx-auto space-y-8">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Fill all the fields</h4>
</div>
<div class="card-body">
<%= render partial: "settings/profile_form", locals: { form_url: update_confirmed_profile_settings_path } %>
</div>
</div>
</div>
Step 5: Update your User model's factory
Step 6: Update all your integration tests.
there is no way you can do it without a validation on every request, because you always have to be sure they fill the profile, no matter on what page you enter.
maybe you can add that validation to the application controller with a before action so you don't have to do it in every controller. and just validate if user is logged in and user.profile...
What do you mean ugly? If it's a matter of "the code will be duplicated everywhere", then you can create a controller that inherits from ActionController, and add before_action to that controller specifying that the profile needs to be filled up. Or you can create a concern which you can then plop in
class SomethingController < ApplicationController
before_action :ensure_profile_filled
def ensure_profile_filled
redirect_to 'wherever'
end
end
class ChildController < SomethingController
end
There are also route constraints but I think you want to do this on the controller layer.

Ruby No Method Error on a Model for Current User when Logged Out

I'm building a rails app that has a lot of things that change based on whether or not the user has completed certain quizzes. Everything works fine when a user is logged in, but I just tried logging out and I got a NoMethodErroron this line of code:
<% if current_user.per_quiz.nil? %>
Basically, if the user has completed per_quiz they go to one page (the edit view), if they haven't they go to a different page (the new view). The only thing that changed was the fact that I logged out, so I can only assume that is what caused the error. Do I need to add some kind of if statement to account for a state in which no user is logged in? How should I fix this error in accordance with Ruby best practices?
You just need to check to see if current_user is set before checking per_quiz. You can handle checking for the login state easily in a view by querying current_user.nil?:
<% if !current_user %>
<p> You must be logged in to do anything useful. Please go login</p>
<% elsif !current_user.per_quiz %>
<p>Cool quiz stuff goes here...</p>
<% else %>
What you probably really want is to have a logged out user go elsewhere, such as the home page or signin page. To do that, you need to do a couple of simple things to your controller. I'm going to assume that the controller is called 'QuizzesController' since you hadn't included your controller code in the question.
Here's how to do it:
class QuizzesController < ApplicationController
# Other devise authentication logic goes here...
before_action :authorize_user!
# Your actions go here...
private
def authorize_user!
if !current_user
redirect_to '/', notice: "You must be logged in to access to this page"
end
end
end
What this does is install a "before_action" handler that will check that the user is logged in before letting them do anything in this controller. If they are not logged in, you can redirect them wherever you wish.
Note that sometimes, only certain actions need this kind of treatment. In that case, you can use an :only or :except option to specify which action(s) are or are not handled. It looks like this:
before_action :authorize_user!, only: [ :new, :edit, :create ]
or like this:
before_action :authorize_user!, except: :list
This will give you greater flexibility in managing the authorization part of the equation, where devise handles the authentication part.
That's because current_user is nil when no user logged in. And if you will cal some method on nil it will throw NoMethodError.
I assume that have code you provided in the view. So, you can check if user is logged in.
<% if user_signed_in? %>
# do smth with user quizzes
<% else %>
# do something else
<% end %>
But the best way is to use before filter in the controller and not allow to anonymous to see that page.
Here is the example application with Rails and devise. https://github.com/RailsApps/rails-devise
Hope that helps.
I'm not sure if this is the "correct" Ruby way to do this, but the way I eventually found was to change it to an if/elsif/else statement:
<% if current_user.nil? %>
...
<% elsif current_user.bal_quiz.nil? %>
...
<% else %>
...
<% end %>
Probably should have figured this out before I posted the question, but I was (and still am) interested to see if there's a better or "more Ruby" way of doing this.

Redirect URL via User Input

I would like to know how I would take the user's input and use it to redirect them to a new URL. For example if the user entered "556859" into the input field and clicked "Go" it would take them to www.website.com/556859.
I'm using ruby on rails but am not sure how to achieve my goal. Any input would be appreciated.
If you need examples for the view and the rest as well, then ask. But this controller method should be enough to point you in the right direction:
def redirect_action
redirect_to "/#{params[:redirect_path]}"
end
You can send user input to controller method
and in that method you can use redirect_to helper method
For example
in view create form with action="controller/redirect"
and in controller create method redirect
and with params[] use your input
Best regards
You'd do something like this:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def redirect
redirect_to root_path + param
end
end
#config/routes.rb
post "", to: "application#redirect", as: :redirect
#app/views/application/index.html.erb
<%= form_tag redirect_path do %>
<%= f.input :param %>
<%= f.submit "Go" %>
<% end %>
--
The next question becomes "why?"
Rails' routing infrastructure is such that it should give you the ability to access individual views for what you're trying to accomplish.
For example, if you're trying to show users in that redirect, you can do:
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
class UsersController < ActionController::Base
def show
#user = User.find params[:id]
end
end
This will give you the ability to access http://yoururl.com/users/12312321
Alternatively, you could also (if you had nothing else on that part of the app), do the following:
#config/routes.rb
resources :users, path: "" #-> http://yoururl.com/12313123
If you know what type of data you're trying to show, it should give us more to work with in regards how you'd construct routing structure for it.

Rails form that redirects to show page

I'm using Rails 4 and have a Policy model with a field policy_number.
I'd like to create a (search-like) form where you input a policy_number and it redirects you to that Policy's show page. I'm not sure how to go about this, should the form's action be policy_path or something?
Thanks!
The biggest problem here is that the user is inputting the policy number in the search form, so you don't have access to it at the time the form is rendered. Without using JavaScript, you won't be able to go directly to the policy by policy number entered.
Here's a possible starting point, though. Create a PolicySearchController with an index method, add a route for it, and create a simple form.
app/controllers/policy_search_controller.rb
class PolicySearchController < ApplicationController
def index
policy = Policy.where(policy_number: params[:policy_number]).first
if policy.present?
redirect_to policy
else
redirect_to :policies, alert: "No matching policy found."
end
end
end
config/routes.rb
resources :policy_search, only: :index
app/views/policies/index.html.erb
<%= form_tag policy_search_index_path, method: :get do -%>
<%= text_field_tag :policy_number -%>
<% end -%>
Now you can iterate on this to add JavaScript, fuzzy matching, etc. if desired.

Active Admin Custom Action Item Resource Name

Don't know why I can't seem to figure this out since it seems like it should be so simple, but basically, I'm trying to create a link to an action (I want "Publish" to appear next to show, edit, delete) for each of a resource in Active Admin.
I used the code they suggest on their wiki:
action_item do
link_to "button label", action_path(post)
end
Problem is, I get an error because rails doesn't know what "post" is. It's nil. The version of the Wiki on Github has the same code, except they use "resource" instead of post. I wasn't sure if that was them implying that I would use my own resource name there, or if you're supposed to actually use the variable "resource". I tried the latter case and got a "Couldn't find without an ID" error.
So the question is, where do I set the variable name? What are they using as their iterator?
I used to use this:
action_item only: :show do |resource|
link_to('New Post', new_resource_path(resource))
end
UPDATE
action_item only: :show do
link_to('New Post', new_resource_path)
end
Thanks Alter Lagos
I have accomplished this with a very similar piece of code, see:
Inside my: app/admin/posts.rb
member_action :publish, method: 'get' do
post = Post.find(params[:id])
post.publish!
redirect_to admin_post_path(post), notice: 'Post published!'
end
In my case, I want the link buttons available only in the show action.
action_item :only => :show do
if post.status == 'pending'
link_to 'Publish', publish_admin_post_path(post)
elsif post.status == 'published'
link_to 'Expire', expire_admin_post_path(post)
else
end
end
Hope this helps you!
In ActiveAdmin you have to use resource to reference an object that you're working with.
When you use resource in an action like index, you will probably get an error as ActiveAdmin is not working with one. To prevent this, specify the actions you want the button to appear in.
To specify an action, give the argument only with an array of the actions you want the button to appear in. For example:
action_item :only => [:show, :edit] do
...
end

Resources