Disable autoincrementing id inside of ActiveRecord::Base.transaction - ruby-on-rails

I have a FormObject for registration which creates a user and a lot of models for him inside create method.
def create
ActiveRecord::Base.transaction do
#user = User.create(#params[:user])
process_bonuses_for_user
process_actions_for_user
# et.c., giant amount of methods
end
#user.persisted? # to return the true of false to controller
end
I met the strange behaviour of my FormObject. Even it ran successfull (create a lot of models) or unsuccessful (not saving them), the id of User model is autoincrementing. So, every trying to save something using my FormObject increment the value of next id for User. This is normal situation when User created successfully, but not normal when user makes a mistake on registration form.
How can I disable this unpredictable behaviour?
P.S. I know that everything is work when I write #user = User.new(#params[:user]) at the start of create method and #user.save at the end, but there are a lot of associations, and I don't want to write a lot of autosave or inverse_of in my models.
P.P.S. I'm postgresql-9.4 user

Your transaction is not working because you're using create. You need to use the bang version (create!) to raise an exception on failure which triggers the rollback. Do note that you'll need to rescue the InvalidRecord exception yourself.

In my opinion, it could be something like this:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
process_bonuses_for_user
process_actions_for_user
# et.c., giant amount of methods
...
format.html { redirect_to ... }
end
end

Related

When #User is activated create an Object that belongs to that User

I have a User model that has many galleries and categories(both separate models that belong_to :user). I would like to create a default category named "Everything" and a default gallery entitled "Everything".
I am adapting Mike Hartl's tutorial for my app, specifically the User creation and activation part. So, after a user is created using this code:
User.rb
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = 'Please check your email to activate your account.'
redirect_to root_url
else
render 'new'
end
end
An activation email is sent to the User and if they click on the link it will activate their account. At the point of activation, so the activated column switches from false to true, I would like the "Everything" gallery and category to be created. In my mind that is the most logical place for this to happen because it is likely to only be triggered once. That way I dont' have to worry about an "Everything" category and gallery trying to be created everytime someone logs on to their account.
The way I am trying to create these is by inserting the following code immediately after the account is activated:
user.categories = user.categories.build(name: 'Everything')
So it looks like, this:
account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
user.categories = user.categories.build(name: 'Everything')
log_in user
flash[:success] = 'Account activated!'
redirect_to user
else
flash[:danger] = 'Invalid activation link'
redirect_to root_url
end
end
end
My problem is that while the User is created, the "Everything" category is not and I get the following error, which I can't reconcile:
NoMethodError at /account_activations/0hbg0T33tjQlleiwKqvUDg/edit
undefined method `each' for #<Category:0x00000004f95b60>
I don't know where 'each' would be called in this process.
Though I mentioned creating a gallery and category and user activation, the above code only address category. It seems to me that if I can get a category to work I should be able to get the gallery to work using a similar solution. This is what my create action looks like for category:
def new
#category = current_user.categories.build
end
def create
#category = current_user.categories.create(category_params)
if #category.save!
respond_to do |format|
format.html
format.js
end
end
end
I have a couple questions related to my issue:
The first is the the most obvious, why am I getting the nomethod error, there doesn't appear to be an 'each' method associated with categories?
Am I approaching this issue correctly? Should I trigger the creation of my default category and gallery from somewhere else in the code? I've read a bit about a .change? that I tried to use on the activated column on User, but I couln't figure out how to use it.
Any help you can offer is greatly appreciated.
You are trying to assign the result of user.categories.build(name: 'Everything') to user.categories. user.categories.build(name: 'Everything') creates the new record on it's own. You do not need to further assign it to user.categories. Trying to do so is causing Rails to iterate on the result of user.categories.build(name: 'Everything'), which is a single Category, not a collection.
So the solution to your question 1 is to replace:
user.categories = user.categories.build(name: 'Everything')
with:
user.categories.build(name: 'Everything')
The answer to question 2 depends on how many different places and/or ways a user can be activated. If the controller action for activating is the only place, your solution is probably fine. But if you ever want to activate a user another way (from the console, for example), you would probably be better off using a an ActiveRecord callback on User, such as :after_save and checking for user.activated_changed? && user.activated?, then generating the generic category and gallery there.
In either case, you may want to consider checking that the generic category and gallery don't already exist for the user (in case they were once activated and then deactivated), before creating them.
I figured out what I was doing wrong. I was trying to create categories/an array instead of a category/a single object. I turned this:
user.categories = user.categories.build(name: 'Everything')
to this:
user.categories << user.categories.create(name: 'Everything')

Possible to pass a record through Rails redirect_to?

I'm attempting to manage user sign ups and log in with omniauth. I'd also like to collect more information about users after they authorize with the provider. At the moment Vendor#from_omniauth returns either an existing vendor or a new Vendor record with their auth hashes already populated. Is it possible to pass along the new record to the Vendor controller so the form can easily use it?
I'm using Rails 4.
class SessionsController < ApplicationController
def create
vendor = Vendor.from_omniauth(env["omniauth.auth"])
if vendor.new_record?
redirect_to new_vendor_url(vendor)
else
session[:vendor_id] = vendor.id
redirect_to root_url, notice: "Signed in!"
end
end
end
This issue was resolved by changing new_vendor_url(vendor) to new_vendor_url(vendor: vendor.attributes).
HTTP is stateless, so you'll need to either include info in the params, in the session (via cookies or other), or in the database.
Given that you seem to be doing a registration process, I might be inclined to put the info in the database as soon as you get it. It's an additional write and then read, but if the user drops out midway through the process, you can still contact them (in theory) to resume the signup.
Pass the vendor.id to as an argument to new_vendor_url, then do the lookup in the rendered action:
# app/controllers/sessions_controller.rb
redirect_to new_vendor_url(:vendor_id => vendor.id)
# app/controllers/vendors_controller.rb
def new
vendor_from_session = Vendor.find(params[:vendor_id])
# code here
#vendor = Vendor.new
end

How to redirect output of user defined class to view in rails

I am facing the following problem.
I am making a naive Bayes gender prediction algorithm in rails. For that I have coded it and put my code file in roots directory. I am able to access the class in my Model controller and run the code by appropriate calling of class.
I have a form with three fields "weight, height and gender", when I fill all these three, they should get saved in the db. I have written its logic and it works well. In case I leave out the gender field as it is, it should run my code and render an appropriate view.
In the create action of controller I am doing the following:
def create
#user = User.new(params[:user])
if #user.gender.blank?
#b_obj= Bayes.new
#b_obj.predict(#user[height], #user[weight]).
else
if #user.save
redirect_to #user
else
#title = "Train"
render 'new'
end
end
end
This code works well. Now I am faced with the problem, how to redirect the result of my code (first if statement) to a view and where to make it. My code returns "male" or "female" as an output. Should I store it in some variable? how should I go about it?
This is the last thing left in my project. After trying many approaches I have come to this, but now I am in sort of a deadlock.
Regards,
Arun
You could add an extra boolean attribute to your user object called 'gender_predicted' which would indicate whether the gender set for the user is predicted or was specified:
#user.gender = #b_obj.predict(#user[height], #user[weight])
#user.gender_predicted = true
That would allow you to store your prediction and only have to run it once. Then you can treat all users the same way but still allow you to track where predictions have been used.
def create
#user = User.new(params[:user])
#user.gender ||= Bayes.new.predict(#user[height], #user[weight])
if #user.save
flash[:notice] = 'User saved'
redirect_to #user
else
#title = "Train"
render 'new'
end
end

Post Redirect Get pattern in Rails

How can I implement PRG in Rails?
I used PRG in Rails, but I am not totally convinced it's right. I was wondering is there any better way to handle it in Rails?
I don't know how popular PRG pattern is and why one has to religiously stick to the "redirect" on failure aspect of it (actually, one good reason is sometimes you dont want to deal with the "setup" complexity at create failure and keep things dry).
What you basically need is to transfer the params for :user to new. I think #Hitesh's solution above is quite close.
class UsersController < ApplicationController
def new
if flash[:user_params]
#user = User.new(flash[:user_params])
#user.valid?
else
#user = User.new
end
end
def create
#user = User.new(params[:user])
if #user.save
# clears previously stored user if there is any
flash[:notice] = "User created."
redirect_to '/'
else
flash[:error] = "Error saving User"
flash[:user_params] = params[:user]
redirect_to :action => :new
end
end
end
Use the session, Luke
The way you implemented it in your blog post is quite fine, however you may want to use session instead of flash to store your #user and optionally use the ActiveRecord session store to keep cookies from getting bloated.
From ActionController::Base documentation
ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and, unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set
config.action_controller.session_store = :active_record_store
in your config/environment.rb and run rake db:sessions:create.
So you should…
class UsersController < ApplicationController
def new
#user = session[:user] || User.new
end
def create
#user = User.new(params[:user])
if #user.save
# clears previously stored user if there is any
session[:user] = nil
redirect_to '/'
else
session[:user] = #user
redirect_to :action => :new
end
end
end
I'm no expert in these matters, but this looks good. From what I understand flash is a part of the session. So the answers telling you to switch to session seem a bit misguided. In this case you want the data to be cleared after the redirect. Other than shoving it in the session, I'm not sure where you would put it.
As far as your cookie size increasing, well, the default session provider for Rails is a cookie in Rails 3. You could swap the session provider out if you wanted to keep the data server side. It is encrypted though, so you are probably okay with the data in the cookie, unless size is an issue.
use below code
class UsersController < ApplicationController
def new
#user = User.new(session[:user_param])
session[:user_param]=nil
end
def create
#user = User.new(params[:user])
if #user.save
# clears previously stored user if there is any
flash.discard(:user)
redirect_to '/'
else
session[:user_param] = #user
redirect_to :action => :new
end
end
end
It is true, though, that you should not do redirect_to '/'. You should define root in your routes file and then do redirect_to root_path.
Edit: Oops, that was supposed to be a comment to SpyrosP's answer.
Also: Here is some excellence guidance on flash. Particularly this may ease your mind:
The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc.
The interesting things there is that, yes it is a part of the session, so answers to "use the session instead of flash" are misguided, as Justin Etheredge's answer already put it. The other thing is that it says it is useful for storing messages instead of only for storing messages. With the added "etc" it would lead me to believe that it is within the intended usage to store user information in there as well.
One last thing, I would agree with Aditya Sanghi that you should just store the user parameters and not an entire user object in the flash.
I didn't read the question properly.
The validation failure you have necessitates going to a different page where a different process will occur. You tried to update a domain object, it doesn't exist. The usual response to a validation failure is to re-render the page, but you need to go to the create page.
The flash hash seems wrong for this. I'd agree with the idea of stuffing your entered data into the session and redirecting.

Rails - Add Record to join table from controller

I'm trying to create a record within a join table from the action of a button. I would have an events model and would like to track selected events from each user.
I used the HABTM relationship since I don't really need any extra fields.
User.rb:
has_to_and_belongs_to_many :events
Event.rb:
has_to_and_belongs_to_many :users
Events_Users Migration:
[user_id, event_id, id=>false]
I'm getting stuck on the actual creation of the record. Someone helped me earlier with adding the record in within the console:
u = User.find(1)
u.events << Event.find(1)
Now I would like to perform the action as a result of clicking a link... Is this in the right direction?
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
if #user.events.save(params[:user][:event])
flash[:notice] = 'Event was saved.'
end
end
Should I add a #user.events.new somewhere and if so where do I put the params of which user and which event?
The following code should work (assuming that you pass in an parameter with the name id that corresponds to the id of an event object):
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
#user.events << #event
flash[:notice] = 'Event was saved.'
end
The problems I see in your code are:
You are passing a hash to .save. Save should only take a boolean value corresponding whether validations should be run and is true by default. However .create and .new can accept a hash of values. (.save would be used after .new).
You load an event through params[:id] but then you attempt to create an event through params[:user][:event]. Which do you want to do? Create or load? (my example assumes load)
Actions that have an effect such as this one should happen when a user clicks a button and submits a form rather than 'clicking a link'. This code may be vulnerable to cross site request forgery (Someone could trick someone into clicking a link on another site that ran this action). Rails forms, if correctly implemented, are protected against this because they use a request forgery protection token.
Most likely you want to redirect the user after this action. Rendering pages after executing actions like this (rather than redirecting) is considered bad practice.
What you did in the console you need to do in the controller.
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
#user.events << #event
flash[:notice] = 'Event was saved.'
end
The thing to note here is that the << operator for existing records will cause the association to be persisted immediately.
Take a look at the ActiveRecord documentation for more info.
If the event_id is passed as params[:id] and you are adding only one event in this call then, you can do the following in your controller code:
User.find(session[:user_id]).events << Event.find(params[:id])
flash[:notice] = 'Event was saved.'
You don't need explicit save to save the has_many association of an existing model instance.
Scenario 1
u = User.new(..)
u.events << Event.first
# Now you need to call `save` in order to save the user object
# and the events association
u.save
Scenario 2
u = User.first
u.events << Event.first
# Don't need to call `save` on `u` OR `u.events`

Resources