I'm a freshman to learn Rails and working on my first project about online book writing.
I've already made the MVC of user,book and section. I wanna create a button called "Author Place",which can show all the pieces written by the current logged in user.
I wanna ask a simple question. How can I make a condition with the current username to select the current author's works from the book database. Should I put this code in controller or view?
Code as follow.
current_user method of the ApplicationController:
protect_from_forgery
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
The Section model :
class Section < ActiveRecord::Base
attr_accessible :book_id, :section_content, :section_tag, :user_username
belongs_to :book
belongs_to :user
end
The Section controller :
class SectionsController < ApplicationController
def userpieces
#sections=Section.find(:all, :conditions=>"user_username=current_user.username") # This part doesn't work
end
end
Or any suggestions with some other way to do this?
Assuming you have a corresponding has_many :sections association in your User model, try this:
#sections = current_user.sections
As depa and izuriel mentioned, you should be able to get it simply if your model relation is correctly set.
Anyway, if you wish to get it in the way you try please use:
#sections=Section.find(:all, :conditions => ["user_username= ?",current_user.username])
Please note, in rails 3, .find(:all is deprecated, please use .all instead.
Related
I am using the obfuscate_id gem ( https://github.com/namick/obfuscate_id ).
We obfuscate ID's by inserting one line into the top of each model:
obfuscate_id
It works great and as expected. My ID's are obfuscated.
However, as part of some logic in my ApplicationController, I have some logic to check the current user and each controller has access to these methods as helpers:
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
However, I get an error:
Couldn't find User with id=5164061535
It doesn't seem to be able to convert the obfuscated ID back to its normal form for a find().
How can I get the controllers to recognise this obfuscation that's made in each model.
My user model is like so:
class User < ActiveRecord::Base
# This part obfuscates the user ID
obfuscate_id
has_one :profile, dependent: :destroy
has_many :pins
has_many :replies, through: :pins
end
Any ideas how I can get the ApplicationController to recognise this? Doing find() in each controller itself is fine, but as ApplicationController doesn't have its own model, it doesn't seem to know of it.
Thanks,
Michael.
Try this...
#current_user ||= User.find(User.deobfuscate_id(session[:user_id])) if session[:user_id]
Weirdly enough, I ended up trying this:
#current_user ||= User.find_by_id(session[:user_id]) if session[:user_id]
And it worked! But, why?
User.find() in itself was not working with this gem. So, although it's now working, it concerns me a little as to why exactly.
If anyone could add anything here that'd be great.
Thanks!
Rails form validation is designed to go in the model most easily. But I need to make sure the current user has the required privileges to submit a post and the current_user variable is only accessible in the controller and view.
I found this answer in a similar question:
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.`
How can I set this up with my post and user controller so that the current_user variable is accessible in the model?
Solution:
This whole thing is wrong from an application design perspective as #Deefour's answer pointed out. I changed it so my view doesn't render the form unless the condition is true.
The "similar question" is saying you can do something like this
class YourModel < ActiveRecord::Base
attr_accessor :current_user
# ...
end
and then in your controller action you can do something like
#your_model = YourModel.find(params[:id])
#your_model.current_user = current_user
#your_model.assign_attributes(params[:your_model])
if #your_model.valid?
# ...
You can then use self.current_user within YourModel's validation methods.
Note I don't think this is what you should be doing though, as I don't consider this "validation" as much as "authorization". An unauthorized user shouldn't even be able to get the part of your action where such an update to a YourModel instance could be saved.
As for doing the authorization with Pundit as requested, you'd have a file in app/policies/your_model.rb
class YourModelPolicy < Struct.new(:user, :your_model)
def update?
user.some_privilege == true # change this to suit your needs, checking the "required privileges" you mention
end
end
Include Pundit in your ApplicationController
class ApplicationController < ActionController::Base
include Pundit
# ...
end
Then, in your controller action you can do simply
def update
#your_model = YourModel.find(params[:id])
authorize #your_model
# ...
The authorize method will call YourModelPolicy's update? method (it calls the method matching your action + ? by default) and if a falsy value is returned a 403 error will result.
Authorization shouldn't be done in models. Models have already many responsibilities don't you think?
That's a controller thing, and actually you can have the logic in other place using some gem like cancan and in your controller you would do something like:
authorize! :create, Post
You can define a "virtual attribute" in your model like this:
class Book < ActiveRecord::Base
attr_accessor :current_user
end
Its value can be set directly in your controller like this:
class BooksController < ApplicationController
def create
book = Book.new
book.current_user = current_user
book.save!
end
end
And inside your model's validation routine, you can access it like any other ActiveRecord field:
def validate_user_permission
errors[:current_user] = "user does not have permission" unless current_user.is_gold?
end
I can't remember if this is the case with ActiveRecord, but you might be able to set virtual attributes via the mass-assignment methods like create, update, and new in the controller:
def create
Book.create!(current_user: current_user)
end
In order to do that, you would probably have to add the following line to your model to enable mass-assignment of that virtual attribute:
attr_accessible :current_user
I agree with Ismael - this is normally done in the controller. It's not an attribute of the model, it's a permission issue and related to the controller business logic.
If you don't need all the power of a gem like CanCan, you can role your own.
class BooksController < ApplicationController
before_filter :gold_required, :only => :create
def create
book = Book.new
book.save!
end
# Can be application controller
private
def gold_required
return current_user && current_user.is_gold?
end
end
You may want to put the filter on the 'new' method as well.
I have a User & Profile Models. A user has_one profile IE.
class User < ActiveRecord::Base
has_secure_password
# Relationships
has_one :profile
class Profile < ActiveRecord::Base
# Relationships
belongs_to :user
I am then trying to test to see if the user has a profile. If not redirect them to the profile controller ie.
class User::BaseController < ApplicationController
# Filters
before_filter :check_for_profile
# Layout
layout "backend"
# Helpers
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def check_for_profile
if current_user.profile.empty?
redirect_to new_user_profile_path, :notice => "Please create a profile first."
end
end
end
No matter what I try I'm getting the error.
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
I'm pretty sure my relationships are right. Can anyone shed some light on what I'm doing wrong ?
Thank you in advance.
Lee
try profile.blank? instead. empty? is not defined for nil.
Check out the "blank?" method at the following link. The "present?" method should also be considered - they're basically the same.
http://api.rubyonrails.org/classes/Object.html#method-i-blank-3F
I'm having an issue coming up with a good way to do the following. I have a very generic Org model and User model. Org has_many :users, and User belongs_to :org.
I am trying to find a way of showing a list of users that is not restricted by Org, but also show a list of User's that is restricted by Org. I know I could nest the routes, and just have two different routes like
map.resources :users
map.resources :orgs, :has_many => :users
The problem is that they both go back to the same actions in the User controller. The controller code starts to get very messy because I am having to check for the existence of an :org_id param. Then I have to decide whether to return the normal results of a find call on User, or a find that is scoped to an Org. I'm not sure what the best solution is here, or what the best practice is. If someone with some knowledge on this could please enlighten me, it would be great.
Another way of doing this without a plugin would be to use named_scope. You can create a named scope in User that filters by org_id if it's not empty.
class User < ActiveRecord::Base
belongs_to :org
named_scope :by_org, lambda{|org| org.blank? ? {} : { :conditions => ['org_id = ?', org] }}
end
And in the controller just use your named scope. That way if you eventually supply more filter options in the controller you don't need to duplicate them:
class UsersController < ApplicationController
def index
#users = User.by_org(params[:org_id]).all
...
end
end
Not really the answer you were probably looking for on a technical level but I use a plugin for my projects that takes care of that mess for me. Take a look at make_resourceful.
make_resourceful do
actions :all
belongs_to :org
end
It will figure out the rest for you, no need to define your standard crud action. It will even detect scoping and scope it for you. (unless that's an other plugin i'm using I forgot about)
I use resource_controller plugin for most cases. With it, you just put:
class UsersController < ApplicationController
resource_controller
belongs_to :org
end
It works with nested and not-nested resources.
If you don't want to use additional plugin, your controller still won't be very complicated.
class UsersController < ApplicationController
def index
#org = Org.find(params[:org_id]) unless params[:org_id].blank?
#users = params[:org_id].blank? ? User.all : #org.users
...
end
end
What I usually do is this:
class UsersController < ApplicationController
def index
root = Org.find(params[:org_id]) if params[:org_id]
root = User if root.nil?
#users = root.all(:conditions => {...}, :order => "...")
end
end
I'm basically doing a tree-walk. As conditions are added, I simply change the root of the #find call. When I'm done evaluating conditions, I call the final #find / #first / #all method and I'm done.
This also works if you have multiple named scopes:
class UsersController < ApplicationController
def index
root = Org.find(params[:org_id]) if params[:org_id]
root = User if root.nil?
root = root.named(params[:name]) if params[:name]
root = root.registered_after(params[:registered_at]) if params[:registered_at]
# more conditions, as required
#users = root.all(:conditions => {...}, :order => "...")
end
end
I have the following ActiveRecord classes:
class User < ActiveRecord::Base
cattr_accessor :current_user
has_many :batch_records
end
class BatchRecord < ActiveRecord::Base
belongs_to :user
named_scope :current_user, lambda {
{ :conditions => { :user_id => User.current_user && User.current_user.id } }
}
end
and I'm trying to test the named_scope :current_user using Shoulda but the following does not work.
class BatchRecordTest < ActiveSupport::TestCase
setup do
User.current_user = Factory(:user)
end
should_have_named_scope :current_user,
:conditions => { :assigned_to_id => User.current_user }
end
The reason it doesn't work is because the call to User.current_user in the should_have_named_scope method is being evaluated when the class is being defined and I'm change the value of current_user afterwards in the setup block when running the test.
Here is what I did come up with to test this named_scope:
class BatchRecordTest < ActiveSupport::TestCase
context "with User.current_user set" do
setup do
mock_user = flexmock('user', :id => 1)
flexmock(User).should_receive(:current_user).and_return(mock_user)
end
should_have_named_scope :current_user,
:conditions => { :assigned_to_id => 1 }
end
end
So how would you test this using Shoulda?
I think you are going about this the wrong way. Firstly, why do you need to use a named scope? Wont this just do?
class BatchRecord < ActiveRecord::Base
belongs_to :user
def current_user
self.user.class.current_user
end
end
In which case it would be trivial to test. BUT! WTF are you defining current_user as a class attribute? Now that Rails 2.2 is "threadsafe" what would happen if you were running your app in two seperate threads? One user would login, setting the current_user for ALL User instances. Now another user with admin privileges logs in and current_user is switched to their instance. When the first user goes to the next page he/she will have access to the other persons account with their admin privileges! Shock! Horror!
What I reccomend doing in this case is to either making a new controller method current_user which returns the current user's User instance. You can also go one step further and create a wrapper model like:
class CurrentUser
attr_reader :user, :session
def initialize(user, session)
#user, #session = user, session
end
def authenticated?
...
end
def method_missing(*args)
user.send(*args) if authenticated?
end
end
Oh, and by the way, now I look at your question again perhaps one of the reasons it isn't working is that the line User.current_user && User.current_user.id will return a boolean, rather than the Integer you want it to. EDIT I'm an idiot.
Named scope is really the absolutely wrong way of doing this. Named scope is meant to return collections, rather than individual records (which is another reason this fails). It is also making an unnecessary call the the DB resulting in a query that you don't need.
I just realized the answer is staring right at me. I should be working from the other side of the association which would be current_user.batch_records. Then I simply test the named_scope on the User model and everything is fine.
#Chris Lloyd - Regarding the thread safety issue, the current_user attribute is being set by a before_filter in my ApplicationController, so it is modified per request. I understand that there is still the potential for disaster if I chose to run in a multi-threaded environment (which is currently not the case). That solution I suppose would be another topic entirely.