I have an issue with an automatically generated token. In a model, I generate the token automatically using:
class User < ApplicationRecord
before_create :generate_confirm_token
def generate_confirm_token
self.confirm_token = generate_token
end
def generate_token
loop do
token = SecureRandom.hex(10)
break token unless User.where(confirm_token: token).exists?
end
end
After creating of user, the token is generated correctly, but the issue is in a controller:
class Companies::StudentsController < ApplicationController
def create
#company = Company.find(params[:company_id])
#student = #company.students.create(student_params)
raise #student.inspect
if #student.save
StudentMailer.with(student: #student).welcome_email.deliver_now
redirect_to company_students_path
else
render :new
end
end
student contains confirm_token BUT in params the confirm token is empty.
I need the token in params because in the mailer I use Find_by(params[:confirm_token]).
Here is how I use a confirm_token in my view. I assume I need the confirm_token in params so I have to have it in a view also:
<%= f.hidden_field :confirm_token %>
The process which is described above is OK.
The issue was in mailer.
student should be in mailer created like this:
#student = params[:student]
but I did it like this:
#student = Student.find_by(confirm_token: :confirm_token)
Which is not correct according to the mailer documentation:
Any key value pair passed to with just becomes the params for the
mailer action. So with(user: #user, account: #user.account) makes
params[:user] and params[:account] available in the mailer action.
Just like controllers have params.
Related
I have a custom field phone for a User that is not stored in the database, since I'm using it for other purposes. Each field is in a different partial and can be updated on it's own via AJAX. This is how I handle this in the UsersController:
app/controllers/users_controller.rb
def update
#user = ...
if #user.update_attributes(params[:user])
respond_to do |format|
format.json { render json: {html: render_to_string partial: 'edit_field', locals: { user: #user }} }
end
end
end
I have the submitted phone param available in a custom User model attribute custom_attrs:
app/models/user.rb
include UserConcern
...
attr_accessor :custom_attrs
...
# I've shorten this method, so that you can get the idea, might not work in reality
def update_attributes
self.custom_attrs[:phone] = self.phone
end
I can't add that to the #user object before I return the response in the controller, I get ActiveModel::MissingAttributeError: can't write unknown attribute 'phone'.
In my view the phone field value is set by a method in the UserConcern:
app/models/concerns/user_concern.rb
def phone
self.phone = fetch_phone(user) # this method returns the users' phone value
end
When I update the phone and re-render the partial, it still has the same value, because the #user object doesn't have phone, since it's not a field in the model.
I also can't do send("phone=", value), because there's no such method in the controller (results in NoMethodError: undefined method 'phone_number_mobile=' for #<UsersController:)
So - how do set the value for phone to the newly updated object #user, either by the #user.custom_attrs or the UserConcern?
There is a simpler way.
app/models/user.rb
...
attr_accessor :phone
...
You can then access user.phone
When I try to create a user, Rails returns a ParameterMissing error:
ActionController::ParameterMissing in UserController#create
param is missing or the value is empty: user
My Controller
class UserController < ApplicationController
def create
#user = User.new(user_params)
render json: #user
end
private
def user_params
params.require(:user).permit(:fame, :lame, :mobile)
end
end
My User Model
class User < ActiveRecord::Base
self.table_name = "user"
end
What am I doing wrong?
Check your logs for the params that are being sent to your controller. Most likely, the params hash being sent by your view doesn't include the :user, key. To fix, you'll need to make sure your form_for is properly namespaced with a User object:
form_for #user do |f|
# ...
end
You can also use the as key to explicitly set the :user key in your params.
form_for #object, as: :user, method: :post do |f|
# ...
end
Update
Since the questioner was using postman to send data, the data sent to the server should be properly formatted like so:
user[firstName]
Thanks #Fabio and #Anthony
When you asked about the form, I actually realized that the parameter I sending with postman was actually incorrect as they should be like
user[firstName]
Updated
It actually deepns upon you how you send the params.
I send as
user[firstname] So I get like params[:user][:firstName]
If I send like firstname So this will be params[:firstName]
I've read several SO links on this topic. Even if you can hack it to get current_user in model, you shouldn't do it. So, what are my options in my case?
I'm using the devise_invitable gem, and one of the commands is User.invite!({:email => email}, current_user), which stores who the user is invited by (current_user). I'd like to have this information.
Currently, users are invited to join a private group, and this process is handled in my group.rb model:
# group.rb
def user_emails
end
def user_emails=(emails_string)
emails_string = emails_string.split(%r{,\s*})
emails_string.each do |email|
user = User.find_for_authentication(email: email)
if user
self.add user
GroupMailer.welcome_email(user)
else
User.invite!(email: email) # But I want this: User.invite!({:email => email}, current_user)
user = User.order('created_at ASC').last
self.add user
end
end
end
If relevant, it's just a text_area that receives these emails to process:
# groups/_form.html.erb
<%= f.text_area :user_emails, rows: 4, placeholder: 'Enter email addresses here, separated by comma', class: 'form-control' %>
Without having to re-arrange too much, how can I run User.invite!({:email => email}, current_user) in this process, so that this useful information (who is invited by whom) is stored in my database? Much thanks!
Update:
With #Mohamad's help below, I got it working.
# group.rb
def emails
end
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.group_invite user
else
add User.invite!({:email => email}, inviter)
end
end
end
# groups_controller.rb
def update
#group = Group.friendly.find(params[:id])
if #group.update_attributes(group_params)
emails = params[:group][:emails]
#group.invite_many(emails, current_user) # also put this in #create
redirect_to #group
else
flash[:error] = "Error saving group. Please try again."
render :edit
end
end
And then nothing in my User model because User.invite is defined already by devise_invitable and I didn't need to do anything else. This process is working great now!
There are some subtle issues with your code. There's a potential race condition on the else branch of your code where you try to add the last created user. I'm also unsure that you need a setter method here unless you are access emails from elsewhere in the instance of Group.
As suggested by others, pass the current user as an argument form the controller. I'm not sure how invite! is implemented, but assuming it returns a user, you can refactor your code considerably.
I would do somethng like this:
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.welcome_email user
else
add User.invite!(email, inviter)
end
end
end
# controller
#group.invite_many(emails, current_user)
# User.invite
def invite(email, inviter)
# create and return the user here, and what else is necessary
end
If you are calling user_emails() from the controller (and I'm guessing you are as that must be where you are receiving the form to pass in emails_string), you can pass in the current_user:
user_emails(emails_string, current_user)
and change user_emails to receive it:
def user_emails=(emails_string, current_user)
You can store the current_user with global scope ,like #current_user,which can be assigned in sessions controller,so in model you will just #current_user as the current user of the app.
I have a model Token with three fields user_id,product_id and unique_token.In the controller i instantiate a #token object with user_id and product_id values collected from the form.Then i call save_with_payment function with that object,where within the function i want to generate random string 3 times and save in unique_token field.The problem is self.tokens.create!( unique_token: Digest::SHA1.hexdigest("random string") ) give me no method error undefined method tokens.What am i doing wrong here?To clarify what i want to accomplish,I want to be able to retrieve list of generated unique_tokens associated to that user_id or product_id like User.find(1).tokens or Product.find(1).tokens.The model association is User has_many Tokens Product has_many Tokens.Note: unique_token field is from Token model originally,user_id and product_id are just ref primary keys.Much Thanks!
def create
#token=Token.new(params[:token])
if #token.save_with_payment
redirect_to :controller => "products", :action => "index"
else
redirect_to :action => "new"
end
end
class Token < ActiveRecord::Base
require 'digest/sha1'
def save_with_payment
# if valid?
# customer = Stripe::Charge.create(amount:buck,:currency => "usd",card:stripe_card_token,:description => "Charge for bucks")
#self.stripe_customer_token = customer.id
3.times do
self.tokens.create!(unique_token: Digest::SHA1.hexdigest("random string"))
end
save!
end
end
There is no tokens method on the Token class. Since you're creating three tokens you don't need the #token instance. Just have save_with_payment be a class method:
def create
if Token.save_with_payment(params[:token])
redirect_to :controller => "products", :action => "index"
else
redirect_to :action => "new"
end
end
class Token < ActiveRecord::Base
require 'digest/sha1'
def self.save_with_payment(attributes)
attributes.merge!(unique_token: Digest::SHA1.hexdigest("foo"))
3.times do
self.create!(attributes)
end
end
end
Hope this helps.
You might want to wrap the loop in a begin/rescue, too. Otherwise if the 2nd or 3 create! fails you end up with tokens AND redirecting to "new".
Response to 1st comment:
That won't work if you use a class method. You can't call valid? because you're not in the context of an instance of Token. I don't recommend sticking with an instance method. If you do change it to a class method you'll want to wrap it in a transaction block:
def self.save_with_payment(attributes)
transaction do
attributes.merge!(unique_token: Digest::SHA1.hexdigest("foo"))
3.times do
self.create!(attributes)
end
rescue
false
end
end
That should roll back the SQL transactions if any of the create! calls fail and return false to the controller create action.
I'd pull that customer code out of Token (Token shouldn't care about creating/retrieving a customer) and put it in the controller action. Pass the pertinent information into save_with_payments. Like:
self.save_with_payments(customer, attributes)
...
end
How do I prevent accessing a specific set of records based on a session variable?
i.e. I have a table of items with a user_id key, how do I filter access to the items based on user_id. I don't want someone to be able to access /items/3/edit unless that item has their user id against it (based on session var)
update:
I am using the answer suggested by #fl00r with one change, using find_by_id() rather than find() as it returns a nil and can be handled quite nice:
#item = current_user.items.find_by_id([params[:id]]) || item_not_found
where item_not_found is handled in the application controller and just raises a routing error.
Restrict access by fetching items through your current_user object (User should :has_many => :items)
# ItemsController
def edit
#item = current_user.items.find(params[:id])
...
end
where current_user is kind of User.find(session[:user_id])
UPD
Useful Railscast: http://railscasts.com/episodes/178-seven-security-tips, TIP #5
You can check access in show/edit/update method:
def edit
#item = Item.find(params[:id])
restrict_access if #item.user_id != current_user.id
....
end
and add restrict_access method, for example in application_controller
def restrict_access
redirect_to root_path, :alert => "Access denied"
end