I'm trying to determine the best structure to approach multi level user groups. Thus far I've created one object called "User" which i assumed could potentially be broken into different levels. Or should I simply create different tables for each user group?
Have a look into Single Table Inheritance..
The short version is that you add a type(string) column to your table and subclass all other models that will use that table from User
Eg:
class SuperUser < User
...
end
I assume you are talking about differnt roles for your users. I am currently using RoleRequirement. It gets the job done fairly easily.
http://code.google.com/p/rolerequirement/
As EmFi suggested, single table inheritance is your best bet. You would need to add the type field to the users table in the database and subclass your User model as below:
class Admin < User
# You probably don't need any methods here.
end
but you would also need to create a before filter. Quite similar to the one which makes sure that the user is logged in, it simply checks the class of your user. This should go in your ApplicationController:
def check_admin
current_user.is_a? Admin
end
There you go, Bob's your uncle, you have rudimentary authorisation.
To promote a user to Admin within rails, just change the type field. Of course, in this way, the user can only hold one access level (which is fine for a lot of applications). If you should want a more complex system, acl9 is quite well equipped for the job. I personally make a habit of using it along with authlogic to form quite a powerful system.
Related
I have a Model Bot and I would like to ensure that there is only one Bot object in my database. I also need to make sure it is persisted and not tampered with.
My original thought was to do this in a migration, one that would follow the :bots table migration. It would include a line that is something like:
Bot.all.size == 0 ? Bot.create! : nil
Maybe this would prevent the AR object from being messed with in future migrations?
BONUS: Would be awesome to be able to have instant and global access to this class object. I was thinking using a singleton module in my Bot class that way I can always reference Bot.instance and have access to that specific object.
USE CASE:
I have 4 types of users in my DB and this bot will be the facilitator to delivery role-specific messages to them through our in-app messaging feature.
The Class Bot will have a has_many association with BotMessage/bot_messages. On the bot_messages table will be an enum field for user_role.
Messages will be created by company admins and stored in these tables because we want them to be viewable at any time by looking at the "conversation" thread between the User and the Bot.
When it comes to only having 1 bot, it's just that. I have no need for an additional Bot object. Additionally, since there is only one object it would be nice to be able to have a way of explicitly targeting that object without having to run a query to find it.
For example, unlike User where there could be 1000 records and in order to find the specific one you would do something like #user = User.find_by_email('foo#bar.com'), doing something like that for the bot would be unnecessary since there is only one record to find. That is what lead me to believe having a singleton object may be worthwhile here, so whenever I need to pull up a message for a specific role, I could run Bot.instance.bot_messages.where(user_role: 1) or something similar
Based on your Use Case, I see no reason for Bot to be a model.
Let's say you have a role called cool_user and you want to get all the bot_messages for that role, you might do something like:
class Bot
class << self
def bot_messages(user_role)
BotMessage.send(user_role)
end
end
end
As a very thoughtful but potentially anonymous super code monkey notes in the comments, you could also do:
class Bot
def self.bot_messages(user_role)
BotMessage.send(user_role)
end
end
Which some folks might find more readable. IMO, it is a bit of an issue of personal preference.
In either case, you should be able to do
Bot.bot_messages(:cool_user)
Since, as stated in the docs,
Scopes based on the allowed values of the enum field will be provided as well.
So, I believe BotMessage, with the properly set enum, should respond to cool_user and return all the bot_messages for that role.
You may need to check the docs to get the syntax exactly right.
I believe this should also satisfy your BONUS requirement.
A proven solution would be to use an STI on User (with a user_type column)
class User < ApplicationRecord
...
end
class Bot < User
has_many :bot_messages, foreign_key: :user_id
end
Is it what you're looking for ?
I've created 2 tables, one for users and one for admins.
I created 2 tables as they both collect different information, but I want to be able to allow a sign in using an email address and password from both the admin and user tables via the same form.
Is this possible? I've looked around and people seem to have created 1 users table and added an admin boolean, but I wanted to avoid this and I didn't want to collect unnecessary data if I didn't need to.
Any help and assistance about how to best go around this would be great.
If you are implementing something from scratch, then it is simply a matter of coding it. I think this approach has some inherent flaws and I would avoid it.
If you want to have some segregation on the model side of things, I suggest you use STI. That way there is some shared behaviour/attributes and the distinctions can be coded separately, so you have your protection.
If you have plenty of distinct attributes, I would suggest separating them from your user/admin and creating an "admin_profile" model that belongs_to :admin and a "user_profile" that belongs_to :user.
And to make coding "transparent", you can create accessors in your admin model class to get/set the profile attributes seamlessly. Say you have an is_cool attribute on the admin_profile model, but you'd like to access it as
imadmin.is_cool
You can have in your admin.rb model
has_one :admin_profile
def is_cool
self.admin_profile.is_cool
end
be careful cause the has_one relationship may return nil if there is no profile associated with the admin/user.
I have a User model that has a number of additional attributes (like settings and some log info) that are only required in certain situations and contain a fair amount of data.
If I was selecting a user from the database myself then I'd use something like this in the majority of cases (where I didn't need those extra attrs);
standard_attrs = [:id, :username]
User.select(standard_attrs).find(params[:user_id])
(That's just an example case, standard_attrs would contain more than those attrs, but not the full set of user attrs.)
With devise it selects * from users when it loads the current_user object. Is there a way to change what devise selects when it loads current_user? Or would the better solution be to move the less frequently required attrs onto a separate model?
Thanks
I would move additional data to separate model, you can use draper gem because it is obviously Decorator pattern.
At the following link:
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Look at the step 7:
"For cases where callback logic only needs to run in some circumstances or including it in the model would give the model too many responsibilities, a Decorator is useful."
So you have some logic ( get additional data for certain user ). Since this data is in database, this model should persist in database as well.
IMHO Decorator is way to go. You actually don't need draper for this, you can do this with PORO(plain old ruby object).
Bottom line, create another model with user id, create function in user for running query to get this data :
def additional_attributes
AdditionaAttributes.find(self.id)
end
And use it like that. Since you have some logic to decide when to call it, you won't have any problems.
NOTE: I used term decorator because it is closest description. Implement this like PORO extended from ActiveModel and you are good to go.
I know the dogma says to not access current_user in a model but I don't fully agree with it. For example, I want to write a set of logging functions when an action happens via a rails callback. Or simply writing who wrote a change when an object can have multiple people write to it (not like a message which has a single owner). In many ways, I see current_user more as config for an application - in other words make this app respond to this user. I would rather have my logging via the model DSL rather than in the action where it seems REALLY out of place. What am I missing?
This idea seems rather inelegant Access current_user in model
as does this: http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
thx
edit #1
So my question isn't if there are gems that can do auditing / logging. I currently use paper_trail (although moving away from it because I can do same functionality in approx 10 lines of ruby code); it is more about whether current_user should never be accessed in the model - I essentially want to REDUCE my controller code and push down logic to models where it should be. Part of this might be due to the history of ActiveRecord which is essentially a wrapper around database tables for which RoR has added a lot of functionality over the years.
You've given several examples that you'd like to accomplish, I'll go through the solution to each one separately:
I want to write a set of logging functions when an action happens via
a rails callback
Depending on how you want to log (DB vs writing to the logger). If you want to log to the DB, you should have a separate logging model which is given the appropriate information from the controller, or simply with a belongs_to :user type setup. If you want to write to the logger, you should create a method in your application controller which you can call from your create and update methods (or whatever other actions you wanted to have a callback on.)
Or simply writing who wrote a change when an object can have multiple people write to it
class Foo < ActiveRecord::Base
belongs_to :user, as: :edited_by
end
class FooController < ApplicationController
def update
#foo = Foo.find(params[:id])
#foo.attributes = params[:foo]
#foo.edited_by = current_user
end
end
I think you're misunderstanding what the model in Rails does. Its scope is the database. The reason it can't access current_user, is because the current user is not stored in the database, it is a session variable. This has absolutely nothing to do with the model, as this is something that can not exist without a browser.
ActiveRecord::Base is not a class that is designed to work with the browser, it is something that works with the database and only the database. You are using the browser as an interface to that model, but that layer is what needs to access browser specific things such as session variables, as your model is extending a class that is literally incapable of doing so.
This is not a dogma or style choice. This is a fact of the limitations of the class your model is extending from. That means your options basically boil down to extending from something else, handling it in your controller layer, or passing it to the model from your controller layer. ActiveRecord will not do what you want in this case.
The two links you show (each showing imho the same approach) is very similar to a approach I still use. I store the current_user somewhere (indeed thread-context is the safest), and in an observer I can then create a kind of audit-log of all changes to the watched models, and still log the user.
This is imho a really clean approach.
An alternative method, which is more explicit, less clean but more MVC, is that you let the controller create the audit-log, effectively logging the actions of the users, and less the effects on different models. This might also be useful, and in one website we did both. In a controller you know the current-user, and you know the action, but it is more verbose.
I believe your concerns are that somehow this proposed solution is not good enough, or not MVC enough, or ... what?
Another related question: How to create a full Audit log in Rails for every table?
Also check out the audited gem, which solves this problem as well very cleanly.
Hope this helps.
I have a simple model called Party with a corresponding table called parties. There's also a controller with all the usual CRUD actions and so on. This model is used in a website and only one admin user is allowed to edit the parties - everyone else is allowed to call GET actions (index, show). Nothing special so far.
Now I need to do the following: The admin would like to choose a single Party at a time for special presentation (the selected Party is showing up on the start page of the application). The most important thing is, that there's only ONE party at time selected.
How would you solve this problem? Boolean Flag in Party model? Save the selection (id of the party) somewhere outside the database? Implement a new model with a has_one relation to Party (seems like overkill to me)?
I hope my explanation is good enough to understand the issue.
A simple "front_page" attribute would suffice or another model like you mentioned, using the has_one relationship would be fine as well.
Using another model would allow you to maintain some more information, like how long should it remain on the front page (expiration date?) or how many times it was featured (assuming a party can be featured twice). It really depends on other requirements for your system.
You also might be able to get away with a simple implementation of the Singleton pattern as well. There's a quick description on the Rails Wiki of making an ActiveRecord object a Singleton (see below): http://wiki.rubyonrails.org/rails/pages/TipsAndTricks
Making a singleton ActiveRecord object
If you have a table with just one
entry, useful for keeping track of a
number sequence for databases without
sequences, you can use the singleton
module included with ruby like so:
require 'singleton'
class Master < ActiveRecord::Base
include Singleton
def initialize(args=nil) super(args) if record = Master.find(:first)
self.attributes = record.attributes end end def next_tracking_number increment!
(:current_tracking_number) current_tracking_number end def
self.next_tracking_number instance.next_tracking_number
end
end
Update:
This is a very poor code example (was copied and pasted from the Rails Wiki, which had no formatting). I would highly recommend the [Ruby Design Patterns] book which tackles many of the GoF design patterns in greater detail (while making them applicable to Ruby applications). But Google should return you some good resources for using the Singleton pattern in Ruby.2
I would go for the boolean flag and create nested singleton resource (promoted), which I would implement in PartiesController itself (set_promoted_party and get_promoted_party actions). For these I would create two new routes:
PUT /parties/promoted/:party_id # to set the promoted party
GET /parties/promoted/:party_id # to get the promoted_party
I would add a second model that had a has_one relationship in order to keep the app RESTful and simple. Also, this way, you can keep a history of special Parties, and track other meaningful information related to the special parties.
Personally I'm very strong on data integrity being enforced by my database so would probably add that extra table and enforce it as a foreign key constraint there.
It can seem like overkill, but is the only* solution that prevents data integrity issues.
Could you maybe add it as a field to the admin table/model - which would be an enforced foreign key to the party table?
*Another solution would be a database trigger that checks no other rows are the selected party but I tend to shy away from such solutions.
Keep it simple. Put a promoted_party.yml file in your config directory that the controllers write to and read from. The contents can be as simple as this:
---
party_id: 123
Done. If you need more integrity or fancier relationships later, implement that later, not now.
For deployments, just make sure the file is symlinked to a shared directory to survive application upgrades.