I've got a model #record_request that takes in a unique identifier and saves it to the database - the model can be later used to fire a bunch of API requests off to an external database to do it's work.
Thing is, I don't want the user to be able to save the value in the Rails app, if it can't be queried in the remote database.
I've tried creating validations and creating Rspec tests, but all of them require my #current_user variable with all the api_token and referesh_token and other OmniAuth goodies needed to make an autenticated API call...
So I'm kind of stumped. Where is the best practice place to put a validation rule of this kind.
I'm thinking I'll have to put something in my controller on :create that makes the call there and raises errors/flashes that way... but then it seems like I've got a load of code on my controller, and I've fallen for a fat controller anti-pattern, no?
What is the best practice?
Here is an example from a project I'm working on, this code is written in the model
after_validation :should_validate_match?, on: [:create, :update]
def should_validate_match?
if self[:type] == 'Match'
self.answers.each{|ans| self.errors.add(:answer, "match cannot be null") if ans.match.blank?}
end
self.errors.empty?
end
You could test the authentication in custom method triggered by a before_save callback in your model.
Something like:
before_save :do_this
def do_this
if name_of_attribute.blank?
return
else
# api call to test authentication
end
end
If a record request has a reference to user you shouldn't need access to the current_user helper.
Related
I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.
I have a situation where I want to make a request to third-party API(url shortening service) after creating a record in the database (updates a column in the table which stores the short url), in order to decouple the API request from the Model, I have set up an ActiveRecord Observer which kicks in every time a record is created, using after_create callback hook, here is the relevant code:
class Article < ActiveRecord::Base
has_many :comments
end
class ArticleObserver < ActiveRecord::Observer
def after_create(model)
url = article_url(model)
# Make api request...
end
end
The problem in the above code is article_url because Rails Routes are not available in either Model or ModelObservers, same as ActionMailer (similar problem exists in Mails where if we need to put an URL we have to configure "ActionMailer::default_options_url"). In theory accessing routes/request object in Model is considered a bad design. To circumvent the above issue I could include the url_helpers module as described in the following URL:
http://slaive-prog.tumblr.com/post/7618787555/using-routes-in-your-model-in-rails-3-0-x
But this does not seem to me a clean solution, does anybody have a pointer on this issue or any advice on how it should be done?
Thanks in advance.
I would definitely not let your models know about your routes. Instead, add something like attr_accessor :unshortened_url on your Article class. Set that field in your controller, and then use it from your observer. This has the added benefit of continuing to work if you later decide to set your shortened URL asynchronously via a background task.
Edit
A couple of things, first of all.
Let's get the knowledge of creating a short_url out of the model
entirely.
We could nitpick and say that the short_url itself doesn't belong in the model at all, but to remain practical let's leave it in there.
So let's move the trigger of this soon-to-be-background task into the controller.
class ArticlesController < ApplicationController
after_filter :short_url_job, :only => [:create]
# ...
protected
def short_url_job
begin
#article.short_url = "I have a short URL"
#article.save!
rescue Exception => e
# Log thy exception here
end
end
end
Now, obviously, this version of short_url_job is stupid, but it illustrates the point. You could trigger a DelayedJob, some sort of resque task, or whatever at this point, and your controller will carry on from here.
So, here's my problem. I currently am building a simple authentication system for a rails site. I have 3 classes for this: Person, Session, and Role. In my Person model I have defined method_missing to dynamically capture roles according to this guide.
In my application_controller I have some logic to deal with logins and log-outs, the result of which gives me the currently logged in user via:
#user = #application_session.person
Where #application_session is the current session
Now in one of my controllers, I don't want anyone to be able to do anything unless they are an admin, so I included:
before_filter #user.is_an_admin?
This raises a NoMethodError, even though I have method_missing defined in my model. I tried defining is_an_admin?, having it always return true as a test, and that works.
According to this question, I think the problem might have something to do with proxy associations. When I run:
puts #user.proxy_owner
I get a session object, since each user (Person) can have many sessions, and I got my user (Person) from the current session.
I am very confused why #user.is_an_admin? is not calling the method_missing method in my Person controller. Please let me know if you need more information or code snippets.
I am using Rails 3 on Ruby 1.9
I'd consider a method_missing an overkill for such task.
Now, if you have Session class, which belongs_to User, then you can have this:
class Session
belongs_to :user, :extend => PermissionMixin
end
class User
include PermissionMixin
end
module PermissionMixin
def admin?
if cond
true
else
false
end
end
end
P.S. Check cancan, perhaps it'll suit your needs better.
I use a similar permissions check in my system to check the User > Role > Permissions association:
User.current_user.can_sysadmin?
In my controllers I have to instead use:
User.current_user.send('can_sysadmin?')
This may work for you as well.
I have solved this by moving the method_missing method to my application_controller.rb. I change the logic of the method a little to check for a user, and if found, dynamically check the role. If things were not kosher, I had the method redirect to root_url or return true if the user matched the requested roles.
Finally, in my reports controller, I used before_filter :is_an_admin? and got my desired results. However, I am still unclear as to why method_missing had to be defined in my application controller as opposed to directly in the Person (aka #user) model?
I have a callback on my ActiveRecord model as shown below:
before_save :sync_to_external_apis
def sync_to_external_apis
[user, assoc_user].each {|cuser|
if cuser.google_refresh
display_user = other_user(cuser.id)
api = Google.new(:user => cuser)
contact = api.sync_user(display_user)
end
}
end
I would like to write an rspec test which tests that calling save! on an instance of this model causes sync_user to be called on a new Google instance when google_refresh is true. How could I do this?
it "should sync to external apis on save!" do
model = Model.new
model.expects(:sync_to_external_apis)
model.save!
end
As an aside, requesting unreliable resources like the internet during the request-response cycle is a bad idea. I would suggest creating a background job instead.
The usual method for testing is to ensure the results are as expected. Since you're using an API in this case that may complicate things. You may find that using mocha to create a mock object you can send API calls would allow you to substitute the Google class with something that works just as well for testing purposes.
A simpler, yet clunkier approach is to have a "test mode" switch:
def sync_to_external_apis
[ user, assoc_user ].each do |cuser|
if (Rails.env.test?)
#synced_users ||= [ ]
#synced_users << cuser
else
# ...
end
end
end
def did_sync_user?(cuser)
#synced_users and #synced_users.include?(cuser)
end
This is a straightforward approach, but it will not validate that your API calls are being made correctly.
Mocha is the way to go. I'm not familiar with rspec, but this is how you would do it in test unit:
def test_google_api_gets_called_for_user_and_accoc_user
user = mock('User') # define a mock object and label it 'User'
accoc_user = mock('AssocUser') # define a mock object and label it 'AssocUser'
# instantiate the model you're testing with the mock objects
model = Model.new(user, assoc_user)
# stub out the other_user method. It will return cuser1 when the mock user is
# passed in and cuser2 when the mock assoc_user is passed in
cuser1 = mock('Cuser1')
cuser2 = mock('Cuser2')
model.expects(:other_user).with(user).returns(cuser1)
model.expects(:other_user).with(assoc_user).returns(cuser2)
# set the expectations on the Google API
api1 - mock('GoogleApiUser1') # define a mock object and lable it 'GoogleApiUser1'
api2 - mock('GoogleApiUser2') # define a mock object and lable it 'GoogleApiUser2'
# call new on Google passing in the mock user and getting a mock Google api object back
Google.expects(:new).with(:user => cuser1).returns(api1)
api1.expects(:sync_user).with(cuser1)
Google.expects(:new).with(:user => cuser2).returns(api2)
api2.expects(:sync_user).with(cuser2)
# now execute the code which should satisfy all the expectations above
model.save!
end
The above may seem complicated, but it's not once you get the hang of it. You're testing that when you call save, your model does what it is supposed to do, but you don't have the hassle, or time expense of really talking to APIs, instantiating database records, etc.
I have a permission model in my app, that ties (Users, Roles, Projects) together.
What I'm looking to learn how to do is prevent a user for removing himself for their project...
Can you give me feedback on the following?
class Permission < ActiveRecord::Base
.
.
.
#admin_lock makes sure the user who created the project, is always the admin
before_save :admin_lock
def before_save
#Get the Project Object
project = Find(self.project_id)
if project.creator_id == current_user.id
# SOME HOW ABORT OR SEND BACK Not Allowed?
else
#continue, do nothing
end
end
end
Is that look like the right approach?
Also, I'm not sure how to do the following two things above:
How to abort prevent the save, and send back an error msg?
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Thanks for reading through
How to abort prevent the save, and send back an error msg?
return false during the callback chain tells activemodel to stop (similar to how adding errors to the model during a validation tells it to stop at that point)
self.errors.add_to_base "msg" will add an error to the model, which can then be rendered on the view.
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Models shouldn't really know about things like the current request, if at all possible, you should be locking things down at the controller/action level.
EDIT:
So, the role of controllers is to deal with everything involved in getting the correct information together based on the request, and passing it to the view (which becomes the response). People often say "make your models fat and your controllers skinny", but that could be said of any system that embraces object oriented design -- your logic should be in objects when possible.
That being said, the whole point of controllers is to deal with routing the right things to the right places, and authentication is definitely a concern of routing.
You could easily move the line comparing creator_id to user id in the action, and react based on that.
Now, sometimes you genuinely need that stuff in the model and there is no way around it. That becomes a problem, because you need to fight rails to get it there. One way would be to attr_accessor a current_user field on your model, and pass that in on initialize. Another would be to remove the fields from the params hash that a user is not allowed to change in the action. Neither is really that nice though.
Agreed with Matt that you should try to use the controller for the redirect. The model should have the logic to determine if the redirect is appropriate. Maybe something like
class ProjectsController < ApplicationController
def update
redirect_to(projects_url, :alert => "You can't remove yourself from this project.") and return if Role.unauthorized_action?(:update, params[:project])
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
...
end
class Role
def self.unauthorized_action?(action, params)
# your logic here
end
You should check out CanCan for some ideas.
In permission model take one field project_creater as boolean
In project modelbefore_create :set_project_ownership
def set_project_ownership
self.permissions.build(user_id: User.current.id, project_creater: true)
end
In project controllerbefore_filter :set_current_user
In Application controllerdef set_current_user
User.current = current_user
end