Rails roles and permissions - how do I do it right? - ruby-on-rails

I've sucessfully rolled my own roles and permissions based authorisation system:
Roles and Permissions are both models in the database that have a many-to-many relationship through another table.
Users belong_to a role and have_many permissions through that role
Admin users are able to create new roles and define which permissions go with which roles.
Controllers and Views check the current user has particular permissions before acting or rendering certain things.
It all works wonderfully. Here's a key part of the code which gets included into application_controller,rb:
def permitted_action (permission_names)
# if the users permissions do not instersect with those given then redirect to root
redirect_to(root_url) if (permission_names & current_user.permissions.map(&:name)).empty?
end
and here's a controller validating user permissions:
before_action only: [:new, :create] do
permitted_action ['create_user']
end
My problem is that permissions are DB rows with unique string names, and I use the names when I want to identify the permissions. So I seed the DB with all the permissions that I need, and then when a View or Controller needs to check the permission I need to get the permission name right at that point. If I check for permission "add_user" but in the db the permission name is "add_users" it goes wrong.
Then in testing I have to put all the permissions in again as fixtures, and get all the names right again.
When I add functionality that requires more permissions I have to add these permissions in with the same string in at least 3 different places. That seems like the wrong thing to me.
Ideally the controllers would define what the permissions are that they use, and the db/seeds.rb file would pick up those and seed the db, and the test fixtures would also pick them up, but I'm not sure if this is possible.
How should I structure this?
Edit:
I think what would help me is a fixtures parser that feeds the db/seeds.rb file. Taking something like this from permissions.yml:
archive_tally:
name: archive_tally
description: Archive or make active any tally
category: 3
And spitting out something like this:
archive_tally = Permission.find_or_initiate_by_name("archive_tally")
archive_tally.description = "Archive or make active any tally"
archive_tally.category = 3
archive_tally.save!
I don't think this sort of thing exists yet.

You can actually cut this down to just being the user table (or roles too depending on how many there are) by adding fields like is_admin or is_general_user to group your users into permissions groups. Then the permissions are just a matter of methods on the User Model, for example:
def can_create_new_roles?
self.is_admin
end
So now you can just do
before_action only: [:create, :new] do
redirect_to root_path unless current_user.can_create_new_roles?
end
Which reads much nicer. Plus since this all happens on the User model vs. on its own DB table it would make testing for all of these UserPermissions much easier.

This is what I've come up with and it satisfies me, though it's not the ideal solution that I was hunting for in the question:
It turns out that I want the production database to be seeded with fixtures that are a subset of the fixtures I want appearing in the testing database.
I've made a directory db/seed_fixtures that includes files such as roles.yml and permissions.yml. In these files I've put the fixtures I want to seed the production database with.
In tests/fixtures/roles.yml I've included the other fixtures with this line at the top of the file:
<%= ERB.new(IO.read(Rails.root.join "db/seed_fixtures/roles.yml")).result %>
Same deal with permissions.yml and other YAML files this applies to. I put any testing-only fixtures in there under that include line.
In db/seeds.rb I generate seeding code from the fixtures for production like this: ActiveRecord::Fixtures.create_fixtures("#{Rails.root}/db/seed_fixtures", "roles")
Now I write all my fixtures once each, some for use as seeds in the production database, others are only for testing. To load the seeds in to the database I run rake db:seed (which I do on the production server) and to load all the testing fixtures in the database I run rake db:fixtures:load
Update:
I've just started using Cucumber and I've realised that I can use the code in step 3 above in a Cucumber Step to get the seed data into Cucumber's test database:
Given(/^seed data is loaded into the database$/) do
ActiveRecord::FixtureSet.create_fixtures("#{Rails.root}/db/seed_fixtures", "roles")
_(Role.count).wont_equal 0
end
Cucumber also informed me that ActiveRecord::Fixtures is deprecated in favour of ActiveRecord::FixtureSet

I've come up with a completely different solution: I've created a fixture parser.
Most of my first solution still applies (see my other answer) but now, instead of step 3 in that solution, I've made a parser that reads the fixtures that I want to use for seeds and adds them to the database, checking for whether they are already there or not. I run the parser from db/seeds.rb
I've put the code for the parser up as a gist on github

Related

Rails Engine: Create dummy model for relations?

I am trying to make a Rails Engine that can be plugged into my applications and manage friendships between users. To do that, all the logic dealing with friend requests, acceptance, etc, is going to live in a Rails Engine.
When I create my Friendship model, it needs a belongs_to relation for two Users (two friends). But, I don't want to tie a user to this engine. I want this engine to work generically with any User an application has established.
What technique does one use to create a dummy User that is never to be included in the host application? (I want to avoid a migration of the engine pulling in this dummy User.)
Update:
I removed the second question, pertaining to how to then override the engine's User with the host app's User. I found the answer to that in the guides (http://edgeguides.rubyonrails.org/engines.html#configuring-an-engine).
tl;dr
cd into engine's dummy app root
run rails g model User
run rake db:migrate
Discussion
I came here looking for an answer, but seeing how there were no answers, I will share how I ended up solving this.
When creating a mountable Rails engine (via something like rails plugin new . --mountable --dummy-path=spec/dummy), Rails will generate a "dummy" app for your engine that will serve as the main app an engine would normally be required into.
Us RSpec users use the "--dummy-path" directive and put the dummy app in /spec folder, but for you it may be elsewhere. Find where it is and cd into that dummy app's root.
Once inside the dummy app, call rails g model User to generate a User model for the dummy app. Run rake db:migrate to add the table.
Now you have a placeholder User model that should function acceptably in tests as an association.
You may want to define some mock methods on User, do so in the model definition file located in /path/to/dummy/app/models/user.rb
Naturally, you can make a full-fledged model with associations and logic instead of just a placeholder right there in dummy.
Migrations and code in dummy app are not used when an engine is included in an app,as rake railties:install:migrations will show.
This might be a bit hacky!
I have an app with several engines. Each engine needs to call a limited amount of functionality in the other engines.
For example, Engine1 and Engine2 don't depend on (require or load) each other. However, a model such as Engine1::User might need to call a selection of records using a scope called all_active from the Engine2::Department model. That's okay inside the context of the overall Rails app into which both engines are loaded as Gems.
The problem arises for the developer of Engine1 when they want Engine2::Department.all_active, and they don't have it.
The solution I am using is to create a "mocked model" (for want of a better phrase) in Engine2's test/dummy/app/models (or spec/dummy/app/models if you're using RSpec) folder.
Something like this will work in Engine1:
# spec/dummy/app/models/engine2/department.rb
require '../../app/models/engine2/department' if File.exist?('../../app/models/engine2/department')
module Engine2
class Department
unless Engine2::Department < ActiveRecord::Base
def self.all_active
[{ id: 1, name: 'Finance', active: true},
{ id: 2, name: 'Sales', active: true}]
end
end
end
end
Because this file is in the dummy app, it won't get found by the main Rails app, so it won't interfere there. It will only get loaded in the dummy rails app during development and testing.
FYI, the unless Engine2::Department < AR::Base detects if the Department model inherits from ActiveRecord already, and if it does, the enclosing code won't be loaded. This is a safety mechanism to prevent the class method all_active from being overwritten by the hard-coded sample data in the hash shown above.
If you have a lot of engines in your system, perhaps you should consider creating a git repo of an engine template that can be pulled into each engine (containing common code, etc).
Your sample data might benefit from being OpenStruct objects, so your hash keys can be accessed using dot notation. You could then write code such as #engine2_departments.first.name rather than #engine2_departments.first[:name].

How to start with performance testing

I am working on the rails 3.1.1 and using socery 0.7.11 for authentication, i need to implement the performance testing where i can test following things
Simulate 100 users signing on and off simultaneously
Simulate 100 users adding new interests simultaneously.
I have go through the following links:
http://rubylearning.com/blog/2011/08/14/performance-testing-rails-applications-how-to/
http://guides.rubyonrails.org/performance_testing.html
And able to do following things
created test folder with test_helper.rb
created test/performance/login_test.rb
created fixtures/users.yml
test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end
/performance/login_test.rb
require 'test_helper'
require 'rails/performance_test_help'
class LoginTest < ActionDispatch::PerformanceTest
fixtures :users
# Refer to the documentation for all available options
self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
:output => 'tmp/performance', :formats => [:flat] }
def test_login
post_via_redirect "http://bc.lvh.me/", :username => users(:youruser).username, :password => users(:youruser).password
#using lvh.me coz login page is on my subdomain
end
end
fixtures/users.yml
one:
username: eagleschasar12
password: chaitanyA1#
two:
username: eaglesmarktell12
password: chaitanyA1#
when i run rake test:profile, i got following result
E
===============================================================================
Error: test_login(LoginTest)
ActiveRecord::StatementInvalid: SQLite3::SQLException: table users has no column named password: INSERT INTO "users" ("username", "password", "created_at", "updated_at", "id") VALUES ('eagleschasar12', 'chaitanyA1#', '2012-06-15 06:43:01', '2012-06-15 06:43:01', 980190962)
So my questions are
Why test command firing insert query?
Do i need to write 100 records of users in fixture file to test the functionality?
How to check session login/logoff?
Also when i select interest types from db my user should logged in the system, so how can i test this scenario in performance testing?
Does this tests run on webrick, which is my local server.
I will try to answer your questions in order as best I can. I'm sure others might give more complete answers for each one individually and they're welcome to, but I hope to get you started so you can know what to look for:
1. Why test command firing insert query?
Your insert queries are run from your fixtures. Personally I user FactoryGirl, but the logic is the same. When you run your fixture, it inserts that object into your database. That way you can do something like fixture :user and it will create a user for you without having to supply all the User object details every time. If you have 5 fixtures in your file, running something like fixture :users will create 5 entries (If someone can confirm this for Fixtures I'd be grateful, as I don't user fixtures).
2. Do i need to write 100 records of users in fixture file to test the functionality?
No you don't. You can use the Faker gem to automatically create as many users (or anything else) as you want with random names, strings, etc. The documentation is here.
3. How to check session login/logoff?
When you create your session, you should be setting the session[user_id] variable, so you can test for that. Needless to say, on logoff (i.e. session destroy) you should be setting it to nil. In any case, when a session is created by setting the session[:user_id] variable, then you can access the session ID using request.session_options[:id]
4. Also when i select interest types from db my user should logged in the system, so how can i test this scenario in performance testing?
Before each test you should run a "test_log_in" function that logs in the test user you create. Then the tests that require a logged in user should pass. You should also create tests where without logging in the test user, and they should not perform the changes (or should not display the pages) that require a logged in user.
5. Does this tests run on webrick, which is my local server?
If that is the server you have installed in you Gemfile, that it will be used. It will work with whichever server you have installed.
You have asked a lot of questions in one question, so if you need more detail on the the answers I would recommend you ask each question in a different post with a more detail description on what you're looking for. That way your answers will not be broad like this one, but more specific.
Hope I at least gave you a good place to start from.

How to test ElasticSearch in a Rails application (Rspec)

I was wondering how you were testing the search in your application when using ElasticSearch and Tire.
How do you setup a new ElasticSearch test instance? Is there a way to mock it?
Any gems you know of that might help with that?
Some stuff I found helpful:
I found a great article answering pretty much all my questions :)
http://bitsandbit.es/post/11295134047/unit-testing-with-tire-and-elastic-search#disqus_thread
Plus, there is an answer from Karmi, Tire author.
This is useful as well: https://github.com/karmi/tire/wiki/Integration-Testing-Rails-Models-with-Tire
I can't believe I did not find these before asking...
Prefixing your index-names for the current environment
You could set a different index-name for each environment (in your case: the test environment).
For example, you could create an initializer in
config/initializers/tire.rb
with the following line:
Tire::Model::Search.index_prefix "#{Rails.application.class.parent_name.downcase}_#{Rails.env.to_s.downcase}"
A conceivable approach for deleting the indexes
Assuming that you have models named Customer, Order and Product, put the following code somewhere at your test-startup/before-block/each-run-block.
# iterate over the model types
# there are also ways to fetch all model classes of the rails app automaticly, e.g.:
# http://stackoverflow.com/questions/516579/is-there-a-way-to-get-a-collection-of-all-the-models-in-your-rails-app
[Customer, Order, Product].each do |klass|
# make sure that the current model is using tire
if klass.respond_to? :tire
# delete the index for the current model
klass.tire.index.delete
# the mapping definition must get executed again. for that, we reload the model class.
load File.expand_path("../../app/models/#{klass.name.downcase}.rb", __FILE__)
end
end
Alternative
An alternative could be to set up a different ElasticSearch instance for testing on another port, let's say 1234.
In your enviornment/test.rb you could then set
Tire::Configuration.url "http://localhost:1234"
And at a suitable location (e.g. your testing startup) you can then delete all indexes on the ElasticSearch testing-instance with:
Tire::Configuration.client.delete(Tire::Configuration.url)
Maybe you must still make sure that your Tire-Mapping definitions for you model classes are still getting called.
I ran into a quirky bug when deleting my elasticsearch index via tire in my rspec suite. In my Rspec configuration, similar to the Bits and Bytes blog, I have an after_each call which cleans the database and wipes out the index.
I found I needed to call Tire's create_elasticsearch_index method which is responsible for reading the mapping in the ActiveRecord class to set up the appropriate analyzers, etc. The issue I was seeing was I had some :not_analyzed fields in my model which were actually getting analyzed (this broke how I wanted faceting to work).
Everything was fine on dev, but the test suite was failing as facets were being broken down by individual words and not the entire multi word string. It seems that the mapping configuration was not being created appropriately in rspec after the index was deleted. Adding the create_elasticsearch_index call fixed the problem:
config.after(:each) do
DatabaseCleaner.clean
Media.tire.index.delete
Media.tire.create_elasticsearch_index
end
Media is my model class.
I ran into similar issues and here's how I solved it. Bare in mind that my solution builds on top of #spaudanjo solution. Since I'm using spork, I add this inside the spec_helper.rb's Spork.each_run block, but you may add this into any other each/before block.
# Define random prefix to prevent indexes from clashing
Tire::Model::Search.index_prefix "#{Rails.application.class.parent_name.downcase}_#{Rails.env.to_s.downcase}_#{rand(1000000)}"
# In order to know what all of the models are, we need to load all of them
Dir["#{Rails.root}/app/models/**/*.rb"].each do |model|
load model
end
# Refresh Elastic Search indexes
# NOTE: relies on all app/models/**/*.rb to be loaded
models = ActiveRecord::Base.subclasses.collect { |type| type.name }.sort
models.each do |klass|
# make sure that the current model is using tire
if klass.respond_to? :tire
# delete the index for the current model
klass.tire.index.delete
# the mapping definition must get executed again. for that, we reload the model class.
load File.expand_path("../../app/models/#{klass.name.downcase}.rb", __FILE__)
end
end
It basically defines it's own unique prefix for every test case so that there are no in indexes. The other solutions all suffered from a problem where even after deleting the index, Elastic Search wouldn't refresh the indexes (even after running Model.index.refresh) which is why the randomized prefix is there.
It also loads every model and checks if it responds to tire so that we no longer need to maintain a list of all of the models that respond to tire both in spec_helper.rb and in other areas.
As this method doesn't "delete" the indexes after using it, you will have to manually delete it on a regular basis. Though I don't imagine this to be a huge issue, you can delete with the following command:
curl -XDELETE 'http://localhost:9200/YOURRAILSNAMEHERE_test_*/'
To find what YOURRAILSNAMEHERE is, run rails console and run Rails.application.class.parent_name.downcase. The output will be your project's name.

Ruby on Rails 2.3.5: Populating my prod and devel database with data (migration or fixture?)

I need to populate my production database app with data in particular tables. This is before anyone ever even touches the application. This data would also be required in development mode as it's required for testing against. Fixtures are normally the way to go for testing data, but what's the "best practice" for Ruby on Rails to ship this data to the live database also upon db creation?
ultimately this is a two part question I suppose.
1) What's the best way to load test data into my database for development, this will be roughly 1,000 items. Is it through a migration or through fixtures? The reason this is a different answer than the question below is that in development, there's certain fields in the tables that I'd like to make random. In production, these fields would all start with the same value of 0.
2) What's the best way to bootstrap a production db with live data I need in it, is this also through a migration or fixture?
I think the answer is to seed as described here: http://lptf.blogspot.com/2009/09/seed-data-in-rails-234.html but I need a way to seed for development and seed for production. Also, why bother using Fixtures if seeding is available? When does one seed and when does one use fixtures?
Usually fixtures are used to provide your tests with data, not to populate data into your database. You can - and some people have, like the links you point to - use fixtures for this purpose.
Fixtures are OK, but using Ruby gives us some advantages: for example, being able to read from a CSV file and populate records based on that data set. Or reading from a YAML fixture file if you really want to: since your starting with a programming language your options are wide open from there.
My current team tried to use db/seed.rb, and checking RAILS_ENV to load only certain data in certain places.
The annoying thing about db:seed is that it's meant to be a one shot thing: so if you have additional items to add in the middle of development - or when your app has hit production - ... well, you need to take that into consideration (ActiveRecord's find_or_create_by...() method might be your friend here).
We tried the Bootstrapper plugin, which puts a nice DSL over the RAILS_ENV checking, and lets your run only the environment you want. It's pretty nice.
Our needs actually went beyond that - we found we needed database style migrations for our seed data. Right now we are putting normal Ruby scripts into a folder (db/bootstrapdata/) and running these scripts with Arild Shirazi's required gem to load (and thus run) the scripts in this directory.
Now this only gives you part of the database style migrations. It's not hard to go from this to creating something where these data migrations can only be run once (like database migrations).
Your needs might stop at bootstrapper: we have pretty unique needs (developing the system when we only know half the spec, larg-ish Rails team, big data migration from the previous generation of software. Your needs might be simpler).
If you did want to use fixtures the advantage over seed is that you can easily export also.
A quick guess at how the rake task may looks is as follows
desc 'Export the data objects to Fixtures from data in an existing
database. Defaults to development database. Set RAILS_ENV to override.'
task :export => :environment do
sql = "SELECT * FROM %s"
skip_tables = ["schema_info"]
export_tables = [
"roles",
"roles_users",
"roles_utilities",
"user_filters",
"users",
"utilities"
]
time_now = Time.now.strftime("%Y_%h_%d_%H%M")
folder = "#{RAILS_ROOT}/db/fixtures/#{time_now}/"
FileUtils.mkdir_p folder
puts "Exporting data to #{folder}"
ActiveRecord::Base.establish_connection(:development)
export_tables.each do |table_name|
i = "000"
File.open("#{folder}/#{table_name}.yml", 'w') do |file|
data = ActiveRecord::Base.connection.select_all(sql % table_name)
file.write data.inject({}) { |hash, record|
hash["#{table_name}_#{i.succ!}"] = record
hash }.to_yaml
end
end
end
desc "Import the models that have YAML files in
db/fixture/defaults or from a specified path."
task :import do
location = 'db/fixtures/default'
puts ""
puts "enter import path [#{location}]"
location_in = STDIN.gets.chomp
location = location_in unless location_in.blank?
ENV['FIXTURES_PATH'] = location
puts "Importing data from #{ENV['FIXTURES_PATH']}"
Rake::Task["db:fixtures:load"].invoke
end

Is seeding data with fixtures dangerous in Ruby on Rails

I have fixtures with initial data that needs to reside in my database (countries, regions, carriers, etc.). I have a task rake db:seed that will seed a database.
namespace :db do
desc "Load seed fixtures (from db/fixtures) into the current environment's database."
task :seed => :environment do
require 'active_record/fixtures'
Dir.glob(RAILS_ROOT + '/db/fixtures/yamls/*.yml').each do |file|
Fixtures.create_fixtures('db/fixtures/yamls', File.basename(file, '.*'))
end
end
end
I am a bit worried because this task wipes my database clean and loads the initial data. The fact that this is even possible to do more than once on production scares the crap out of me. Is this normal and do I just have to be cautious? Or do people usually protect a task like this in some way?
Seeding data with fixtures is an extremely bad idea.
Fixtures are not validated and since most Rails developers don't use database constraints this means you can easily get invalid or incomplete data inserted into your production database.
Fixtures also set strange primary key ids by default, which is not necessarily a problem but is annoying to work with.
There are a lot of solutions for this. My personal favorite is a rake task that runs a Ruby script that simply uses ActiveRecord to insert records. This is what Rails 3 will do with db:seed, but you can easily write this yourself.
I complement this with a method I add to ActiveRecord::Base called create_or_update. Using this I can run the seed script multiple times, updating old records instead of throwing an exception.
I wrote an article about these techniques a while back called Loading seed data.
For the first part of your question, yes I'd just put some precaution for running a task like this in production. I put a protection like this in my bootstrapping/seeding task:
task :exit_or_continue_in_production? do
if Rails.env.production?
puts "!!!WARNING!!! This task will DESTROY " +
"your production database and RESET all " +
"application settings"
puts "Continue? y/n"
continue = STDIN.gets.chomp
unless continue == 'y'
puts "Exiting..."
exit!
end
end
end
I have created this gist for some context.
For the second part of the question -- usually you really want two things: a) very easily seeding the database and setting up the application for development, and b) bootstrapping the application on production server (like: inserting admin user, creating folders application depends on, etc).
I'd use fixtures for seeding in development -- everyone from the team then sees the same data in the app and what's in app is consistent with what's in tests. (Usually I wrap rake app:bootstrap, rake app:seed rake gems:install, etc into rake app:install so everyone can work on the app by just cloning the repo and running this one task.)
I'd however never use fixtures for seeding/bootstrapping on production server. Rails' db/seed.rb is really fine for this task, but you can of course put the same logic in your own rake app:seed task, like others pointed out.
Rails 3 will solve this for you using a seed.rb file.
http://github.com/brynary/rails/commit/4932f7b38f72104819022abca0c952ba6f9888cb
We've built up a bunch of best practices that we use for seeding data. We rely heavily on seeding, and we have some unique requirements since we need to seed multi-tenant systems. Here's some best practices we've used:
Fixtures aren't the best solution, but you still should store your seed data in something other than Ruby. Ruby code for storing seed data tends to get repetitive, and storing data in a parseable file means you can write generic code to handle your seeds in a consistent fashion.
If you're going to potentially update seeds, use a marker column named something like code to match your seeds file to your actual data. Never rely on ids being consistent between environments.
Think about how you want to handle updating existing seed data. Is there any potential that users have modified this data? If so, should you maintain the user's information rather than overriding it with seed data?
If you're interested in some of the ways we do seeding, we've packaged them into a gem called SeedOMatic.
How about just deleting the task off your production server once you have seeded the database?
I just had an interesting idea...
what if you created \db\seeds\ and added migration-style files:
file: 200907301234_add_us_states.rb
class AddUsStates < ActiveRecord::Seeds
def up
add_to(:states, [
{:name => 'Wisconsin', :abbreviation => 'WI', :flower => 'someflower'},
{:name => 'Louisiana', :abbreviation => 'LA', :flower => 'cypress tree'}
]
end
end
def down
remove_from(:states).based_on(:name).with_values('Wisconsin', 'Louisiana', ...)
end
end
alternately:
def up
State.create!( :name => ... )
end
This would allow you to run migrations and seeds in an order that would allow them to coexist more peaceably.
thoughts?

Resources