"Virtual associations" with Rails - ruby-on-rails

I'm looking for some guidance on how to implement what I am referring to as "virtual associations" into my model in rails.
I'll preface this with the disclaimer that I have considered going down the real associations path (HABTM) but as far as I can see this would conflict with concepts that I already have implemented.
I currently have a Project model which can be associated with User objects via roles.
As an example, a Project may have many site_managers, construction_managers and project_managers.
A site_manager can also be a project_manager for the same or different projects (rules out single table inheritance).
With the Rolify gem this is fairly straightforward to implement. I can assign any of the roles above to a particular user in a s pecific project with sample_user.add_role(:site_manager, sample_project).
One of my goals is to be able to create a form where I can setup a new project, and assign users to roles from using a multi-select list. So as an example, my form would have the following input to assign selected users as site manager for the new project:
= f.input :site_managers, collection: User.all, input_html: { multiple: true } (Formtastic DSL)
This is where things get slightly complicated. I have managed to implement a custom getter/setter for the site_maanagers attribute where I can take a hash of user_ids passed by the form and fetch/update the appropriate records as needed.
However this implementation is far from being similar to that of a real association, where I could do things like adding a single user to the site_managers with sample_project.site_managers << sample_user.
At the moment I am also unable to set the array of site_managers using user instances. My custom setter only takes user_ids as the argument which is a bit cumbersome and not very intuitive when used outside of a form submission implementation. I can easily work around this by checking types inside the setter method but it feel hackery and not very Rails like.
I've tried ditching the whole custom getter/setter and going with a HABTM implementation that uses a join table to manage all these records but I am concerned that this won't scale well if/when we need to add more roles to the project (each role adds an extra column to the join table). It also ends up feeling like I am duplicating functionality/concepts that are already offered with Rolify so in some places I am checking for roles in a join table and in using Rolify in others (i.e. if a user is an admin or has access to a certain resource).
Is there something else I may have overlooked or this the only way of getting this done?
Thanks and I look forward to hearing some of your opinions.
Rog

Related

Dry up Rails Active Record query conditions

In my Ruby on Rails project, I have a mailer that basically prepares a daily digest of things that happened in the system for a given user. In the mailer controller, I am gathering all the relevant records from the various models according to some common pattern (within a certain date, not authored by this user, not flagged, etc) and with minor differences from model to model.
There are half a dozen of models involved here (and counting), and most of them have unified column names for certain things (like date of publishing, or whether an item is flagged by admin or not). Hence, the 'where's that go into query are mostly the same. There are minor differences in conditions, but at least 2 or 3 conditions are exactly the same. I easily assume there may be even more similar conditions between models, since we are just starting the feature and haven't figured out the eventual shape of the data yet.
I basically chain the 'where' calls upon each model. It irritates me to have 6 lines of code so close to each other, spanning so far to the right of my code editor, and yet so similar. I am dreaded by the idea that at some point we will have to change one of the 'core' conditions, munging with that many lines of code all at once.
What I'd love to do is to move a core set of conditions that goes into each query into some sort of Proc or whatever, then simply call it upon each model like a scope, and after that continue the 'where' chain with model-specific conditions. Much like a scope on each model.
What I am struggling with is how exactly to do that, while keeping the code inside mailer. I certainly know that I can declare a complex scope inside a concern, then mix it into my models and start each of queries with that scope. However, this way the logic will go away from the mailer into an uncharted territory of model concerns, and also it will complicate each model with a scope that is currently only needed for one little mailer in a huge system. Also, for some queries, a set of details from User model is required for a query, and I don't want each of my models to handle User.
I like the way scopes are defined in the Active Record models via lambdas (like scope :pending, -> { where(approved: [nil, false]) }), and was looking for a way to use similar syntax outside model class and inside my mailer method (possibly with a tap or something like that), but I haven't found any good examples of such an approach.
So, is it possible to achieve? Can I collect the core 'where' calls inside some variable in my mailer method and apply them to many models, while still being able to continue the where chain after that?
The beauty of Arel, the technology behind ActiveRecord query-building, is it's all completely composable, using ordinary ruby.
Do I understand your question right that this is what you want to do?
def add_on_something(arel_scope)
arel_scope.where("magic = true").where("something = 1")
end
add_on_something(User).where("more").order("whatever").limit(10)
add_on_something( Project.where("whatever") ).order("something")
Just ordinary ruby method will do it, you don't need a special AR feature. Because AR scopes are already composable.
You could do something like:
#report_a = default_scope(ModelA)
#report_b = default_scope(ModelB)
private
def default_scope(model)
model.
where(approved: [nil, false]).
order(:created_at)
# ...
end

Allowing admin and users to sign in using same form (rails)

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.

Rails find_by_email multiple tables

I am working on a rails application and i have 3 different user types. These users are potentially very different, so i created models for each of them. Now, they should be able to login thru a single form. So basically i want to say something like 'find_by_email("some_email")', but search over all three tables. It seems, though, that Rails expect you to call 'find_by' with a specific model, like Admin.find_by(). Any suggestions?
Try something like this and assuming that that the email is unique across all the tables
[Model1, Model2, Model3].each do |model|
break if model.find_by_email("email#email.com").present?
end
Hopefully this is early in your development, but the current structure may not be the best possible route. How many different columns are NOT shared by each of the user types? You may want to use a "user role" system, and have that simply be an extra column on your user table.
Than, you can use something like CanCan to manage those roles and what/where they may access.

In a single form, linking two instances of two models with habtm

I am using Rails 3.
I have a Product model and a Group model (a group has_many users, through membership).
I would like to build the new.html.erb form for the product model, and at the end of the form, I would like the user to be able to choose members from which group(s) can have access to the product he wants to add.
So, my goal is to list the groups to which the user belongs to, adding a checkbox for each of them. Then, create the associations between the product inserted and the different groups the user selected when the form is submitted, but I really do not understand how to achieve this, as all the documentation I have read use the BUILD or CREATE method that defines a new instance of group, instead of an existing one.
Is it possible with a nested form, and a HABTM relationship between product and group ? Or should I use a nested form with a has_many_through association using new model product_group_relationship ? Or should I use something else than a nested form ?
I'm quite new in Rails and a little bit lost here, so if some experienced guy could guide me a little bit, it would be very much appreciated!
The form_for helper comes with a nice package of extra methods like: fields_for wich makes you able to add nested attributes for has_many_through relations.
I suggest reading these:
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
And make sure you set your model validations accordingly

multi level user groups

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.

Resources