I am trying to get the current_user value into a model. I know that this is probably not appropriate since models should be kept secluded from this type of interaction but I'm running into a problem. I need to include a current_user within a method in a model and do not know how to do it.
I need to make this happen on an update in my stage controller and pass the current_user to the stage model and have that current_user value available. Any help is appreciated.
def update
if #stage.update_attributes(params[:stage])
redirect_to [#project, #stage], :notice => 'Stage was successfully updated.'
else
render :action => "edit"
end
end
You could also store the current user into Thread.current's hash.
See http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
class User < ActiveRecord::Base
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
end
Related
I'm creating a database of products and want to check when a new product is created, if that product already exists.
99% of the time, the product should have a unique product code, but sometimes this will not be the case when a revised version comes out with the same product code.
I would like to have it so that the database checks if the product code exists already. If it does, then the user can either go ahead and create the product anyway, go to the product that already exists, or cancel.
I am trying to achieve this in the controller but cannot seem to get the syntax of the exists? method correct. Can someone point out where I am going wrong, please?
def create
#product = Product.new(product_params)
if Product.product_code.exists?(#product.product_code)
render 'new'
flash[:error] = "This product already exists."
elsif #product.save
redirect_to #product
else
render 'new'
end
end
You should note that what you are doing in the controller or adding a standard uniqueness validation to the model will not allow duplicate Products to be created at all.
This will just keep sending the user back to the form:
def create
#product = Product.new(product_params)
if Product.exists?(product_code: #product.product_code)
render 'new'
flash[:error] = "This product already exists."
elsif #product.save
redirect_to #product
else
render 'new'
end
end
If you want to simply warn the user once you can attach a virtual attribute on the model and use it as condition for the validation:
class Product < ApplicationRecord
attribute :dup_warning, :boolean, default: true
validate :lax_product_code_uniquenes
def lax_product_code_uniqueness
if new_record? && !dup_warning && Product.exists(product_code: self.product_code)
errors.add(:product_code, 'is not unique - are you sure?')
self.dup_warning = true
end
end
end
Then add the virtual attribute to the form:
<%= form_with(model: #product) do |f| %>
...
<%= f.hidden_input(:dup_warning) %>
...
<% end %>
And you don't need to really do anything in the controller besides add dup_warning to the params whitelist.
def create
#product = Product.new(product_params)
if #product.save
redirect_to #product
else
render 'new'
end
end
def product_params
params.require(:product)
.permit(:foo, :bar, :product_code, :dup_warning)
end
Instead if Product.product_code.exists?(#product.product_code) possibly you should use if #product.product_code.present?
UPD: thanks to all, commented below.
Correct use of exists? is Product.where(product_code: #product.product_code).exists?
I want to save two models in one controller action, or save neither, and return with the validation errors.
Is there a better way than this?
def update
#job = Job.find(params[:id])
#location = #job.location
#job.assign_attributes(job_params)
#location.assign_attributes(location_params)
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
if #job.valid? && #location.valid?
#job.save
#location.save
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
else
render 'edit'
end
end
New version:
def update
#job = Job.find(params[:id])
#location = #job.location
begin
Job.transaction do
#job.assign_attributes(job_params)
#job.save!(job_params)
#location.assign_attributes(location_params)
#location.save!(location_params)
end
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
rescue ActiveRecord::RecordInvalid => invalid
render 'edit'
end
end
Have a look at Active Record Nested Attributes.
Using Nested attributes, you can save associated record attributes through parent.If parent record fails, associated records won't be saved.!
the first thing you'd want to do is delete these two lines
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
and only keep the #save in the if statement. because if one of them is valid, but the other isn't, you'll still save the valid one to the db.
To answer your second question, is there a better way to do this? At first blush, it looks like a job for #accepts_nested_attributes_for. However, accepts_nested_attributes_for is somewhat notorious for being difficult to get working (really it just takes a fare amount of tinkering) and what you're currently doing should get you where you're trying to go, so it's up to you.
You can use validates_associated rails helper:
class Model < ActiveRecord::Base
has_one :location
validates_associated :location
end
Then:
if #job.save
#blah
else
#blah
end
Is enough without having to mess with ActiveRecord#Nested_attributes. It's fastest, but less cleaner. Your choice.
Reference:
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated
So i'm having this issue trying to figure out how to use the build method in rails to create an object once a user completely registers and still have that object connected to the users id. I'm using devise for authentication and the model that needs to be created is called "app".
This is the create method for "app".
def create
#app = App.new(app_params)
#app.id = current_user.id
respond_to do |format|
if #app.save
format.html { redirect_to #app, notice: 'Application successfully created.'}
else
format.html { render action: 'new' }
end
end
end
Im getting this error:
Couldn't find App with id=1
from my multi step form controller:
def show
#user = User.find(current_user)
case step
when :school, :grades, :extra_activity, :paragraph, :submit
#app = App.find(current_user)
end
render_wizard
end
You need an after_create callback in the User model. It makes no sense to mess with the AppController because no forms have been filled up for the app and you have no app_params.
class User < ActiveRecord::Base
after_create :build_initial_app
protected
def build_initial_app
self.create_app
end
end
You can read more about this at the Rails Guides page for ActiveRecord Callbacks.
The problem line in your code is here:
#app.id = current_user.id
Setting an ActiveRecord object's id is a no-no. Think of the id attribute like you would a pointer in C. The system creates it for you, and you can use it to refer to a unique model object.
What you probably want is something along the lines of:
#app.user_id = current_user.id
Or, even better:
#app.user = current_user
To do that, you need to set up an association between your App model and your User model. There's a good tutorial on that here.
Rails 3.2. I am using the following code to associate user_id to the record:
# review.rb
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true, :counter_cache => true
end
# reviews_controller.rb
def create
#review = #reviewable.reviews.new(params[:review])
#review.user_id = current_user.id
if #review.save
flash[:success] = 'Thanks for adding your review.'
redirect_to #reviewable
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #reviewable
end
end
I want to find a way to use this, but it keeps saying that the current_user is not defined, but I could find the current_user object:
def create
#review = #reviewable.current_user.reviews.create(params[:review]
if #review.save
flash[:success] = 'Thanks for adding your review.'
redirect_to #reviewable
else
flash[:error] = 'Error adding review, please try again.'
redirect_to #reviewable
end
end
If you can post your code to what the #reviewable object is, it might help to give a more specific answer. But if you want a one liner, you can do something like this:
#review = #reviewable.reviews.build(params[:review].merge({:user_id => current_user.id})
But personally, i think your original looks better as it's easier to read.
As an additional note, your second example also calls create and save. You don't need to call both, as create saves the object when accepting a hash of parameters. Save is nice to use if you want to initialize an object, modify it in some way, then save it later.
I think that the reason this does not work is because current_user is a method that is not defined on a reviewable.
The reviewable may belong to a user, in which case #reviewable.user.reviews.create... may be valid.
I have a Feature page that belongs to the Car page. That is working exactly how I want to, except for one thing.
After creating, updating or destroying, I want the page to be redirected to the admin_car_path(car) instead of the defaults admin_car_feature_path(car,feature) for create and update and admin_car_features_path(car).
I unsuccessfully searched for that.
ActiveAdmin.register Car do
end
ActiveAdmin.register Feature do
belongs_to :car
end
TIA
right code for updating without skipping validation
controller do
def update
super do |success,failure|
success.html { redirect_to collection_path }
end
end
end
Here is the code for update action for your case. This code goes to the features.rb - admin file:
controller do
def update
update! do |format|
format.html { redirect_to admin_cars_path }
end
end
end
This redirects to the cars index page. So you have the idea. Same for create and destroy actions.
At the current moment accepted answer leads to ignoring validation errors.
This works for me with the latest versions of ActiveAdmin and Rails:
controller do
def update
update! do |format|
format.html { redirect_to collection_path } if resource.valid?
end
end
def create
create! do |format|
format.html { redirect_to collection_path } if resource.valid?
end
end
end
Here is a solution that also works with create_another, using parent and child for model names.
This solution assumes that you show children as part of parent (e.g. via table_for) so you do not need child's index method.
In resource override controller's smart_resource_url and index methods:
controller do
def smart_resource_url
if create_another?
new_resource_url(create_another: params[:create_another])
else
parent_path(params[:parent_id])
end
end
def index
redirect_to parent_path(params[:parent_id])
end
end
Current answer is skipping validations. Some of the other answers are working but partially correct (incorrect use of super or manually validating resource).
Most updated "proper" way to redirect with AA after create and udpate:
controller do
def create
create! do |success,failure|
success.html { redirect_to collection_path, notice: "#{resource.model_name.human} was successfully created." }
end
end
def update
update! do |success,failure|
success.html { redirect_to collection_path, notice: "#{resource.model_name.human} was successfully updated." }
end
end
end
Marcelo, I'm not sure I understand your question, but wouldn't putting this into the update, create and destroy actions in your controller do the trick?
format.html { redirect_to redirect_address }
And make redirect_address whatever you need.