I have information in my rails view that I only want to show up if the user has entered all personal details in the database, such as name, firstname, street and city. I could now do this:
if user.name && user.firstname && user.street && user.street
# show stuff
end
but I don't think that is very elegant and "the rails way". Is there an easier and smarter way to do this?
You can use required in your html form tags and validations in Model Class. Also follow links:
http://guides.rubyonrails.org/active_record_validations.html
http://www.w3schools.com/tags/att_input_required.asp
In your model
class User < ActiveRecord::Base
def has_required_fields?
self.name && self.first_name && self.address && ....
end
end
And in your controller
if user.has_required_fields?
# do whatever you want
end
The "rails way" would be thin controller, fat model. So, in your case, you'd want to create a method inside your User model and use it in your controller afterwards.
User model:
def incomplete?
name.blank? or firstname.blank? or street.blank?
end
User controller:
unless user.incomplete?
# Show stuff
end
in model:
ALL_REQUIRED_FIELDS = %w(name surname address email)
def filled_required_fields?
ALL_REQUIRED_FIELDS.all? { |field| self.attribute_present? field }
end
in your controller:
#user.filled_required_fields?
will return true if all fields filled and false if not.
looks very gracefully :)
Related
I'm trying to save in Note which Employee was the last editor of a `Note'
In a View, I'm able to access the current employee like this:
<h4><%= current_user.employee.id%></h4>
But, in a Model, I can't use current_user.employee.id.
So, I'm trying this in the User model:
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
And this in the Note model:
before_create :record_update
before_update :record_update
protected
def record_update
self.lasteditor_id = User.current.employee.id unless User.current.employee.id.nil?
end
What I'm getting is the last User in the Users table.
Thanks for the help!
current_user gets the logged in user information from the session. You cannot access session variables from model. If you want to update the Note model with the Last employee who viewed it, do it in your controller(most likely show action of your note or any other action you think would be right)
def show
#note = Note.find(params[:id])
#note.update_atribute(:last_viewed_by, current_user.id)
end
You code might look different from above. But this is the idea
I'm pretty sure there is a better way to do what I want to do, so please tell me.
I have an Item model that can either be sold to someone (have a sale_price and a buyer_id) or be passed (not sold to anyone - sale_price of zero and no buyer_id).
Until now I just relied on the user entering the appropriate price/buyer combination, but I'd like to add a second submit button to the Item edit form that just says 'pass'. (<input type="submit" name="pass" value="Pass" />).
Upon submission by pressing that button, I'd like to override whatever sale_price and buyer_id has been selected by the user and set them myself.
I assume I should do a :before_save in item.rb, but I don't know how to detect the button from the model - or if it's even possible (or advised).
Thanks
You can differentiate the commit type in your controller:
def create
item = Item.new(params[:item])
if params[:commit] == "Pass"
item.sale_price = nil
item.buyer_id = nil
end
if item.save
# ...usual rails stuff
end
end
Of course, if you have the commit type in the controller, you can pass it into the model with a virtual attribute and use callbacks if you like:
class Item < ActiveRecord:Model
attr_accessor :pass
before_save :reset_sale_price
private
def reset_sale_price
if pass
self.sale_price = nil
self.buyer_id = nil
end
end
end
class ItemsController < ApplicationController
def create
item = Item.new(params[:item])
item.pass = (params[:commit] == "Pass")
if item.save
#... standard rails stuff
end
end
end
Hope it helps. Cheers!
My client wants all user data encrypted, so I've created a before_save and after_find call back that will encrypt certain properties using Gibberish:
# user.rb
before_save UserEncryptor.new
after_find UserEncryptor.new
# user_encryptor.rb
class UserEncryptor
def initialize
#cipher = Gibberish::AES.new("password")
end
def before_save(user)
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.email = encrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
def after_find(user)
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.email = decrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
private
def encrypt(value)
#cipher.enc(value)
end
def decrypt(value)
#cipher.dec(value)
end
end
Well, when the user first signs up using Devise, the model looks about like it should. But then once the user confirms, if I inspect the user, the first_name and last_name properties look to have been encrypted multiple times. So I put a breakpoint in the before_save method and click the confirmation link, and I see that it's getting executed three times in a row. The result is that the encrypted value gets encrypted again, and then again, so next time we retrieve the record, and every time thereafter, we get a twice encrypted value.
Now, why the heck is this happening? It's not occurring for other non-devise models that are executing the same logic. Does Devise have the current_user cached in a few different places, and it saves the user in each location? How else could a before_save callback be called 3 times before the next before_find is executed?
And, more importantly, how can I successfully encrypt my user data when I'm using Devise? I've also had problems with attr_encrypted and devise_aes_encryptable so if I get a lot of those suggestions then I guess I have some more questions to post :-)
I solved my problem with the help of a coworker.
For encrypting the first and last name, it was sufficient to add a flag to the model indicating whether or not it's been encrypted. That way, if multiple saves occur, the model knows it's already encrypted and can skip that step:
def before_update(user)
unless user.encrypted
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.encrypted = true
end
end
def after_find(user)
if user.encrypted
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.encrypted = false
end
end
For the email address, this was not sufficient. Devise was doing some really weird stuff with resetting cached values, so the email address was still getting double encrypted. So instead of hooking into the callbacks to encrypt the email address, we overrode some methods on the user model:
def email_before_type_cast
super.present? ? AES.decrypt(super, KEY) : ""
end
def email
return "" unless self[:email]
#email ||= AES.decrypt(self[:email], KEY)
end
def email=(provided_email)
self[:email] = encrypted_email(provided_email)
#email = provided_email
end
def self.find_for_authentication(conditions={})
conditions[:email] = encrypted_email(conditions[:email])
super
end
def self.find_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
attributes[:email] = encrypted_email(attributes[:email]) if attributes[:email]
super
end
def self.encrypted_email decrypted_email
AES.encrypt(decrypted_email, KEY, {:iv => IV})
end
This got us most of the way there. However, my Devise models are reconfirmable, so when I changed a user's email address and tried to save, the reconfirmable module encountered something funky, the record got saved like a hundred times or so, and then I got a stack overflow and a rollback. We found that we needed to override one more method on the user model to do the trick:
def email_was
super.present? ? AES.decrypt(super, KEY) : ""
end
Now all of our personally identifiable information is encrypted! Yay!
My Rails application have a User model and a Group model, where User belongs to a Group. Thanks to this, a user can be a admin, a manager, a subscriber, etc.
Until recently, when for example a new admin need to be create on the app, the process is just to create a new normal account, and then an admin sets the new normal account's group_id attribute as the group id of the admin... using some condition in my User controller. But it's not very clean, I think. Because for security, I need to add this kind of code in (for example) User#update:
class UsersController < ApplicationController
# ...
def update
#user = User.find(params[:id])
# I need to add some lines here, just as on the bottom of the post.
# I think it's ugly... in my controller. But I can not put this
# control in the model, because of current_user is not accessible
# into User model, I think.
if #user.update_attributes(params[:user])
flash[:notice] = "yea"
redirect_to root_path
else
render :action => 'edit'
end
end
# ...
end
Is there a clean way to do it, with a Rails plugin? Or without...
By more clean, I think it could be better if those lines from User#update:
if current_user.try(:group).try(:level).to_i > #user.try(:group).try(:level).to_i
if Group.exists?(params[:user][:group_id].to_i)
if Group.find(params[:user][:group_id].to_i).level < current_user.group.level
#user.group.id = params[:user][:group_id]
end
end
end
...was removed from the controller and the application was able to set the group only if a the current user's group's level is better then the edited user. But maybe I'm wrong, maybe my code is yet perfect :)
Note: in my User model, there is this code:
class User < ActiveRecord::Base
belongs_to :group
attr_readonly :group_id
before_create :first_user
private
def first_user
self.group_id = Group.all.max {|a,b| a.level <=> b.level }.id unless User.exists?
end
end
Do you think it's a good way? Or do you process differently?
Thank you.
i prefer the controller methods to be lean and small, and to put actual model logic inside your model (where it belongs).
In your controller i would write something along the lines of
def update
#user = User.find(params[:id]
if #user.can_be_updated_by? current_user
#user.set_group params[:user][:group_id], current_user.group.level
end
# remove group_id from hash
params[:user].remove_key(:group_id)
if #user.update_attributes(params[:user])
... as before
end
and in your model you would have
def can_be_updated_by? (other_user)
other_user.try(:group).try(:level).to_i > self.try(:group).try(:level).to_i
end
def set_group(group_id, allowed_level)
group = Group.find(group_id.to_i)
self.group = group if group.present? && group.level < allowed_level
end
Does that help?
Well if you have a User/Groups (or User/Roles) model there is no other way to go than that you have underlined.
If it is a one-to-many association you can choose to store the user group as a string and if it is a many-to-many association you can go for a bitmask but nonetheless either through business logic or admin choice you need to set the User/Group relation.
You can have several choices on how to set this relationship in a view.
To expand your model's capability I advice you to use CanCan, a very good authorization gem which makes it super easy to allow fine grain access to each resource in your rails app.
I'd like to track how many times a user logs in to my site which is a Rails app. Is there any other call like "created_on or updated_on" that can make a little counter in my model that tracks that kind of info? I'm using restful-authentication currently.
I would add login_count field to your User/Account model. Then change this method in User/Account model:
def self.authenticate(login, password)
return nil if login.blank? || password.blank?
u = find_by_login(login) # need to get the salt
u && u.authenticated?(password) ? u.increase_login_count : nil
end
and add this method to model:
def increase_login_count
self.login_count += 1
self.save
self
end
You could create a column in the user table called login_count or something and then in the SessionsController.create method
if user
user.login_count += 1
user.save(false) #update without validations.
# .... other RestfulAuthentication generated code ....