I am trying to get some auth mechanism working for my webapp (written in Sinatra). Currently I am playing around with sinatra-can which looks great. The issue I now face is that I need access to the specific model from the can method. Lets say I have a route that looks like that:
class ProjMgmt < Sinatra::Base
get '/edit/:project' do
project = Project.where(name: param[:project]).first
authorize! :edit, project
project.to_html
end
end
There are two models defined, Project and Manager. They are stored in a MongoDB (via mongoid, NO datamapper, ActiveRecord or so) and have a has_and_belongs_to_many relation, eg. the relations can be accessed via project.managers or manager.projects.
Now, only managers that have a relation to the particular project should be able to edit the project. What I want to have is something like that on authorize!:
class Ability
include CanCan::Ability
def initialize(user)
can :edit, project if project.managers.include? user
end
end
Obviously, that does not work since Ability does not know about any project.
Is there any nice approach to this? Must not necessarily be CanCan...
Try like this
def initialize(user)
can :edit, Project do |project|
project.managers.include? user
end
end
Related
I'm building a platform in Rails that will act as a basis for a few other Rails applications. Ideally I would like this to be a shared engine, on which the other concrete applications can build.
It would be nice if the applications could extend the base classes that the engine provides. AFAIK this can be done using monkey patching, but that feels 'hacky' to me.
I stumbled onto what looked like the solution, which was to just create 'mirror' classes in the main Rails app, which extend those of the engine:
# SharedEngine/models/shared_engine/post.rb
module SharedEngine
class Post < ActiveRecord::Base
def hello
"Hello"
end
end
end
# App/models/shared_engine/post.rb
require SharedEngine::Engine.root.join('app', 'models', 'shared_engine', 'post')
class Post < SharedEngine::Post
def hello
super + " world"
end
end
However, it looks like there are some autoloading problems. After server startup, it prints "Hello". Then after I save the app model, it says "Hello world".
The Rails engine guide suggests putting shared code into concerns. Is there any other way to get this working cleanly without having to create concerns for every class?
Since you need to create a basis for a few Rails apps to reduce boilerplate i suggest having a look a Rails Application Templates. Using templates you may provide all the necessary scaffolding without monkey-patching any class.
I have an application that was running just fine using Devise for authentication. I added CanCan and I am now having issues with Devise. Given the standard "encrypted_password" and "password_confirmation" attributes if they are not specified as attr_accessible then when the controller tries to do:
#user = User.new(params[:user])
I get the standard MassAssignment error. But if I add them to the attr_accessible list I can then create the user but it will then fail on password validation since the attr_accessible does not let Devise encrypt the password. I suspect the issue is something in my routes since I have both:
devise_for :users, :path_prefix => 'secure'
resources :users
The resources :users is there so that the admin and use standard CRUD type operations to manage CanCan.
Not sure if more detail is needed but this sounds like a core issue that I am stubbing my toe on.
Thank you
I found the problem. Someone had changed the model's attr_accessible list to have encrypted_password not password. Devise is looking for the password in the parameters and then creates the encrypted_password for the model.
Another newbeee programmer has learned a lesson :) So did I, double check changes made by newbes!
Thankyou
From the DataMapper document, I think there are at least four functions need to be called to have database setup:
DataMapper.setup(:default, 'sqlite:///path/to/project.db')
DataMapper.finalize
DataMapper.auto_migrate!
DataMapper.auto_upgrade!
In many DataMapper+Sinatra tutorials I learned that auto_migrate! and auto_upgrade! are not supposed to be called every time the app is loaded on production server. But in the meanwhile many examples call these functions in the main ruby file of the sinatra app, say app.rb, without additional check. And some examples do not call finalize at all. So far I am confused and I am not sure what to do on the production server.
Take this following simple app.rb for example, I have some questions:
Where and when should finalize be called?
When deploying the app first time there is no db file on the production server, how do I have it automatically created? Or do I have to create the project.db file manually?
Since the auto_upgrade! is wrapped in :development block, it won't be called on production server. How am I supposed to upgrade database when I add or remove columns in it?
require 'sinatra'
require 'data_mapper'
configure do
DataMapper.setup :default, "sqlite3://#{Dir.pwd}/project.db"
end
class Book
include DataMapper::Resource
property :id, Serial
property :title, Text
belongs_to :author
end
class Author
include DataMapper::Resource
property :id, Serial
property :name, Text
has n, :books
end
configure :development do
DataMapper.auto_upgrade!
end
get '/:id' do
#author = Author.get params[:id]
erb :list_author_and_his_books # The template has nothing to do with this question, ignore it
end
get '/new' do
# Some code for user to input book or author details
end
get '/create' do
# Some code to create book or author in db
end
Thanks for reading this long post :D
Where and when should finalize be called?
From http://rdoc.info/github/datamapper/dm-core/DataMapper#finalize-class_method
This method should be called after loading all models and plugins.
When deploying the app first time there is no db file on the production server, how do I have it automatically created? Or do I have to create the project.db file manually?
It depends on your hosting arrangement, but the main thing to do is put the running of migrations in a Rake task and run them when the app is deployed. If you're using Sqlite, this would create the database (although on some hosts you are not allowed to update the file system). I don't think it's a good idea to use Sqlite for a production database, but that's your decision.
Since the auto_upgrade! is wrapped in :development block, it won't be called on production server. How am I supposed to upgrade database when I add or remove columns in it?
Use a Rake task. After each deployment you'd run the "db:migrate:up" (or whatever you'd called it) task and it would run the latest migrations. You might get a few ideas from Padrino's Rake tasks for DataMapper
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
I need a suggestion on how to implement "client-server" web apps in ruby. Any guides and best practices are highly appreciated. I'm interested both in Ruby ways and gems required as it is a desired platform, and in general ways and logic to implement such things.
I'm not an excellent ruby programmer or a skilled system designer with years of expirience sadly so I really need your help as I still hope this thing would shine in the end.
The current look of the application should be like this:
DB + Auth DB <-> API App <-> Other Apps:
DB - a database or set of databases with one DB for group of users (regions).
Auth DB - one more DB with personal users' data and login information, probably single even if main databases would be split between regions.
API App - this thing maintains all the logic around data, access control and translations probably to have one translations base for all the apps.
Other Apps - bunch of different applications, tied with API, this could be data providers that pokes API with some data about some user, and UI of different kinds. All of the apps could not have own storage of user-related info and work with the data only through the API.
API App: looks like the best tool to make it is Sinatra. The questions are:
How to organize API in the clear way? Rails makes nice REST path settings and folders structure for models and controllers. Any tips or gems to improve API building expirience?
How to maintain access? Warden isn't looks like a good option as API clients would be the web apps themselves. So I need some kind of auth token. How could it be done? Some sort of custom OAuth provider? The thing is, I don't like the idea to store and pass session cookies through the API, some sort of access token passed with each request. This also
Other Apps: mainly web-based UIs. The logical selection for this part is Rails. The main question is how to implement client-side auth check. Devise is cool, but is it possible to make it work with token, or is it more suitable tool?
Okay, this is going to be a bit longer:
If you are already familiar with Rails, you can have a look at the Rails API gem. The project strives to remove additional cruft from Rails which is not needed for a JSON-based RESTful API.
This may sound smooth, but it has it's downsides. First and foremost, you have the readd everything that is needed for basic rails functionality, that you may be accustomed to, e.g. respond_to. This can be a bit tricky, but is pretty straightforward when you find out which rails module originally provided the functionality usually bundled in Rails within the ActionController::Base.
That being said, let me give you an example of what i did for a small API project I did last week out of curiosity:
Initial Situation
We have a mainly finished Rails App. It all works fine, but it is basically monolithic. Everything is served by the Rails Framework. Luckily for us, all the model logic is bundled into a gem called core. the application essentially lets logged in customers create products which are than searchable through an end user view.
The goal was to provide a RESTful API for this, which can handle concurrency and larger data files (i.e. CSV, XLS) a little bit more efficiently.
Introducing Rails API
The design goal let me to the Rails API gem. The basic installation works like in Rails, except the script is called rails-api, i.e.:
rails-api new jsonapi
The advantage for me here was that I could use the core from the other application, but nothing would stop me from just introducing my own models to the jsonapi application.
That being said, you can do all the standard Rails goodies, like routing, etc. It follows the same convention. then again, standard routes initially react to JSON only, which can be a bit confusing at times.
Let me give you an example of the API Controller that handles products:
class ProductsController < ApplicationController
include ActionController::HttpAuthentication::Token
before_filter :find_product, :except => [:create, :index]
def index
render :json => #products
end
def create
#product = product.new params[:product]
if #product.save
render :json => #product, :status => :created
else
render :json => #product.errors, :status => :unprocessable_entity
end
end
def show
render :json => #product
end
def update
if #product.update_attributes params[:product]
render :json => #product, :status => :ok
else
render :json => #product.errors
end
end
def destroy
if #product.destroy
render :json => #product, :status => :ok
else
render :json => {:note => I18n.t("messages.deletion_impossible")}, :status => :unprocessable_entity
end
end
protected
def find_product
#product = Product.find params[:id]
end
end
It is nothing special. The only thing to note is the second line where ActionController::HttpAuthentication::Token is included explicitly. This is so that your aPI may be secured by HTTP Token. If you want to know more about securing an API, I suggest Ryan Bates' Guide on Railscasts.
In essential, you provide a before filter in the ApplicationController like this:
class ApplicationController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
[...]
before_filter :restrict_access
[...]
def restrict_access
authenticate_or_request_with_http_token do |token, options|
# see if key is valid.
end
end
end
Again, note the second line, you have to include the ControllerMethods manually, otherwise no controller will know about authenticate_or_request_with_http_token.
Extension
You may know extend the API based on the Rails conventions. It works exactly the same way, with the exception that some stuff is intentionally missing by default. I suggest adding JBuilder (Railscast), if you need more flexibility in your JSON Templates.
Great, but what about clients?
Personally, there is a lot of choice when it comes to clients. Ultimately I find that it comes down to what you like most. I can personally recommend a small node.js layer on top of the Rails API which then gets a single page application based on backbone.js in front of it. You can also try AngularJS if you like. You can also build another Rails App around it and call the API from within your controller actions.
It also depends on what platform you want to target - a native application for iOS/Android comes to mind.
The choice I made was node.js + backbone. It currently makes the most sense for me at the time and for the project. The node layer essentially holds the Token necessary to communicate with the API and the backbone application has a small library to talk with the node layer. It can however be a double edged sword, depending on how complex your API will be. For a small example this seems to be fine, but there can be a lot of code duplication just to put the calls from the backbone application through to the Rails API.
Authentication
For authentication you can make customer based API-Keys (Tokens) and then limit the controller logic to only accept data operations which are allowed with that key. You could than manage the session via the node layer. Edit: This is authorization, not authentication. Nothing actually stops you from using Authlogic with Rails API - i have not tested it, but it should work.
I confess that i have not finished this part yet - I hope others can answer this architectural question :-)
I hope I could provide some insights.
P.S.: If you want to test your API i highly recommend httpie (It's awesome!)
I have a rails app called X and a rails mountable engine called Y.
App X has Devise and engine Y has CanCan which works with the hosts apps devise installation. In order to seperate the concerns of host app and mountable engine, the engine has a CanCan ability model as well as the host app.
My problem is the following, as per CanCan documentation you can change the default ability model like so https://github.com/ryanb/cancan/wiki/Changing-Defaults.
My problem is that if I setup the of default ability class in the mountable engines application controller like so:
module Y
class ApplicationController < ActionController::Base
def current_ability
#current_ability = Ability.new(current_user)
end
end
end
I get the following error:
uninitialized constant Ability
(On a side note, when I edit the file and reload the page without restarting the server it works fine)
If however I put the same method into the host app's application controller:
class ApplicationController < ActionController::Base
def current_ability
#current_ability = Y::Ability.new(current_user)
end
end
Everything works as expected (which also means that Devise and CanCan are setup correctly).
Anyone got any ideas of why this is happening? I might not understand mountable engines correctly however I always thought that they are isolated from the host app, thus when accessing a page defined by the mountable engine the host app's application controller shouldn't even be executed.
it is most likely that your engines application controller is not loaded at all. Rails engines behave bad if you have same class name in the main app. here is the issue if you want to look at. I would recommend using the explicit declaration in your engine. Use Y::Ability instead of Ability.