add a column value before saving item to database - ruby-on-rails

Say user submits a form (creates new item in his account).
Before it goes to database - I want to do this:
params[:user] => current_user.id
# make a note of who is the owner of the item
# without people seing this or being able to change it themselves
# and only after that to save it to database
What's the best way to do it?
All I see in controller is this:
def create
#item = Item.new(params[:item])
...
end
And I'm not sure how to change values under params[:item]
(current_user.id is Devise variable)
Tried to do this:
class Item < ActiveRecord::Base
before_save :set_user
protected
def set_user
self.user = current_user.id unless self.user
end
end
And got an error:
undefined local variable or method `current_user'

It should be as simple as:
def create
#item = Item.new(params[:item])
#item.user = current_user
#...
end
You're getting an undefined local variable or method current_user error as current_user is not available in the model context, only controller and views.

Related

Pundit context access

An application defines a pundit user according to its context of shop
def pundit_user
CurrentContext.new(current_user, #shop)
end
In practice, the following policy for Contact class
def initialize(user, contact)
#user = user
#contact = contact
end
def create?
user && (user.admin? || user.editor? || (user.id == contact.user_id))
end
does not work as the user attibutes cannot be accessed.
The following error is returned for the admin attribute of user, nor, upon removing the first two conditions, does not access the user's id.
NoMethodError: undefined method `id' for #<CurrentContext:0x0000000114537220
#user=#<User id: 830861402, email: "shopkeeper#mail.co", name_last: "string", name_first: "string", admin: false, editor: false [...] dconne: nil>,
#shop=#<Shop id: 1039309252, name: "[...]
An attempt to alter the initialization invoking the current context
def initialize(current_context, contact)
#user = user
#shop = shop
#contact = contact
end
fails where #shop is not recognized NameError: undefined local variable or method shop' for #<ContactPolicy:`
How can the #user and #shop values be accessed to establish a working policy?
You have to set up CurrentContext class so you can use it inside the policy classes:
class CurrentContext # <= FIXME: the name is not very descriptive
# maybe `AuthorizationContext`
# NOTE: create reader methods to
# get #user and #shop variables outside of this class.
attr_reader :user, :shop
# NOTE: to make a clear distinction here. `initialize` arguments
# just hold the values that you pass to `new` method.
def initialize(user_argument, shop_argument)
#user = user_argument
#shop = shop_argument
end
end
pundit_user method is what Pundit uses to initialize a policy class:
def pundit_user
CurrentContext.new(current_user, #shop)
end
# authorize(record)
# |
# `->Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
# |
# `->Pundit.policy(pundit_user, record)
# |
# `->ContactPolicy.new(pundit_user, record)
Inside the policy class, just use CurrentContext as any other object:
# #contact or Contact-.
# |
# pundit_user--. |
# v v
def initialize(user, contact)
# NOTE: `user` and `contact` are just local variables
# actual names are irrelevant.
# NOTE: `user` is an instance of `CurrentContext`
#user = user.user # get the user
#shop = user.shop # get the shop
#contact = contact
end
To make it obvious what we should get in the initializer, just rename the argument:
def initialize(user_context, contact)
#user = user_context.user
#shop = user_context.shop
#contact = contact
end

Rails: In a controller, add a value to the params submitted by a form

In my "Users" db I have "email", "name", and "position".
Email and name are submitted through a form, but position is determined in my controller before I write the row for the new user. How can I add position to my alpha_user_params?
def create
position = User.order("position DESC").first
# How can I add position to alpha_user_params????
#user = User.new(user_params)
if #user.save
# success stuff
else
# error stuff
end
end
private
def alpha_user_params
params.require(:alpha_user).permit(:email, :producer, :position)
end
if position is an attribute for user, you can do:
#user = User.new(user_params)
#user.position = position
# #user.position = User.order("position DESC").first
if #user.save
# success stuff
else
# error stuff
end
This does not require you to add anything to your alpha_user_params method. Since, the position attribute is being generated by your controller and will not be added/modified by a user, I would advise you to keep this attribute inside your controller, itself.
user_params should only permit those attributes which will be input/modified by the user.
I had a similar problem, I used before_create in the model:
class User < ApplicationRecord
before_create :get_position
private
def get_position
self.position = YourLibrary.position
end
This will add the position when the user is created. You can also use other callbacks like before_save, check the active record documentation

Reuse same object in other methods within same model

Using Rails 3.2. I have the following code:
# photo.rb
class Photo < ActiveRecord::Base
before_create :associate_current_user
after_save :increase_user_photos_count
after_destroy :decrease_user_photos_count
private
def associate_current_user
current_user = UserSession.find.user
self.user_id = current_user.id
end
def increase_user_photos_count
current_user = UserSession.find.user
User.increment_counter(:photos_count, current_user.id)
end
def decrease_user_photos_count
current_user = UserSession.find.user
User.decrement_counter(:photos_count, current_user.id)
end
end
Before a new record is created, it searches for the current_user. This is alright if it's just 1 new record at a time. But if there are 100 records to be created, it's gonna search for the same current_user 100 times. There is definitely performance issue.
I don't want it to keep finding the current user every time a record is created/photos_count updated, etc.
After refactoring, does this affect other users who are also uploading their photos using their accounts?
Note: For some reasons, I can't use the counter_cache and photos_controller.rb because I am following this example: http://www.tkalin.com/blog_posts/multiple-file-upload-with-rails-3-2-paperclip-html5-and-no-javascript
Thanks.
Use this
def current_user
#current_user ||= UserSession.find.user
end
This will cache the value in the instance variable #current_user unless it's nil (first time in the request), in which case it will set it.

Access current_user in model

I have 3 tables
items (columns are: name , type)
history(columns are: date, username, item_id)
user(username, password)
When a user say "ABC" logs in and creates a new item, a history record gets created with the following after_create filter.
How to assign this username ‘ABC’ to the username field in history table through this filter.
class Item < ActiveRecord::Base
has_many :histories
after_create :update_history
def update_history
histories.create(:date=>Time.now, username=> ?)
end
end
My login method in session_controller
def login
if request.post?
user=User.authenticate(params[:username])
if user
session[:user_id] =user.id
redirect_to( :action=>'home')
flash[:message] = "Successfully logged in "
else
flash[:notice] = "Incorrect user/password combination"
redirect_to(:action=>"login")
end
end
end
I am not using any authentication plugin. I would appreciate if someone could tell me how to achieve this without using plugin(like userstamp etc.) if possible.
Rails 5
Declare a module
module Current
thread_mattr_accessor :user
end
Assign the current user
class ApplicationController < ActionController::Base
around_action :set_current_user
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
end
Now you can refer to the current user as Current.user
Documentation about thread_mattr_accessor
Rails 3,4
It is not a common practice to access the current_user within a model. That being said, here is a solution:
class User < ActiveRecord::Base
def self.current
Thread.current[:current_user]
end
def self.current=(usr)
Thread.current[:current_user] = usr
end
end
Set the current_user attribute in a around_filter of ApplicationController.
class ApplicationController < ActionController::Base
around_filter :set_current_user
def set_current_user
User.current = User.find_by_id(session[:user_id])
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
User.current = nil
end
end
Set the current_user after successful authentication:
def login
if User.current=User.authenticate(params[:username], params[:password])
session[:user_id] = User.current.id
flash[:message] = "Successfully logged in "
redirect_to( :action=>'home')
else
flash[:notice] = "Incorrect user/password combination"
redirect_to(:action=>"login")
end
end
Finally, refer to the current_user in update_history of Item.
class Item < ActiveRecord::Base
has_many :histories
after_create :update_history
def update_history
histories.create(:date=>Time.now, :username=> User.current.username)
end
end
The Controller should tell the model instance
Working with the database is the model's job. Handling web requests, including knowing the user for the current request, is the controller's job.
Therefore, if a model instance needs to know the current user, a controller should tell it.
def create
#item = Item.new
#item.current_user = current_user # or whatever your controller method is
...
end
This assumes that Item has an attr_accessor for current_user.
The Rails 5.2 approach for having global access to the user and other attributes is CurrentAttributes.
If the user creates an item, shouldn't the item have a belongs_to :user clause? This would allow you in your after_update to do
History.create :username => self.user.username
You could write an around_filter in ApplicationController
around_filter :apply_scope
def apply_scope
Document.where(:user_id => current_user.id).scoping do
yield
end
This can be done easily in few steps by implementing Thread.
Step 1:
class User < ApplicationRecord
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
end
Step 2:
class ApplicationController < ActionController::Base
before_filter :set_current_user
def set_current_user
User.current = current_user
end
end
Now you can easily get current user as User.current
The Thread trick isn't threadsafe, ironically.
My solution was to walk the stack backwards looking for a frame that responds to current_user. If none is found it returns nil. Example:
def find_current_user
(1..Kernel.caller.length).each do |n|
RubyVM::DebugInspector.open do |i|
current_user = eval "current_user rescue nil", i.frame_binding(n)
return current_user unless current_user.nil?
end
end
return nil
end
It could be made more robust by confirming the expected return type, and possibly by confirming owner of the frame is a type of controller...

Accessing a variable declared in a controller from a model

I'm using the facebooker gem which creates a variable called facebook_session in the controller scope (meaning when I can call facebook_session.user.name from the userscontroller section its okay). However when I'm rewriting the full_name function (located in my model) i can't access the facebook_session variable.
You'll have to pass the value into your model at some point, then store it if you need to access it regularly.
Models aren't allowed to pull data from controllers -- it would break things in console view, unit testing and in a few other situations.
The simplest answer is something like this:
class User
attr_accessor :facebook_name
before_create :update_full_name
def calculated_full_name
facebook_name || "not sure"
end
def update_full_name
full_name ||= calculated_full_name
end
end
class UsersController
def create
#user = User.new params[:user]
#user.facebook_name = facebook_session.user.name
#user.save
end
end

Resources