I'm working on a Rails (currently 2.3.4) app that makes use of subdomains to isolate independent account sites. To be clear, what I mean is foo.mysite.com should show the foo account' content and bar.mysite.com should show bar's content.
What's the best way to ensure that all model queries are scoped to the current subdomain?
For example, one of my controllers looks something like:
#page = #global_organization.pages.find_by_id(params[:id])
(Note #global_organization is set in the application_controller via subdomain-fu.)
When what I would prefer is something like:
#page = Page.find_by_id(params[:id])
where the Page model finds are automatically scoped to the right organization. I've tried using the default_scope directive like this: (in the Page model)
class Page < ActiveRecord::Base
default_scope :conditions => "organization_id = #{Thread.current[:organization]}"
# yadda yadda
end
(Again, just to note, the same application_controller sets Thread.current[:organization] to the organization's id for global access.) The problem with this approach is that the default scope gets set on the first request and never changes on subsequent requests to different subdomains.
Three apparent solutions thus far:
1 Use separate vhosts for each subdomain and just run different instances of the app per subdomain (using mod_rails). This approach isn't scalable for this app.
2 Use the original controller approach above. Unfortunately there are quite a number of models in the app and many of the models are a few joins removed from the organization, so this notation quickly becomes cumbersome. What's worse is that this actively requires developers to remember and apply the limitation or risk a significant security problem.
3 Use a before_filter to reset the default scope of the models on each request. Not sure about the performance hit here or how best to select which models to update per-reqeust.
Thoughts? Any other solutions I'm missing? This would seem to be a common enough problem that there's gotta be a best practice. All input appreciated, thanks!
Be careful going with default scope here, as it will lead you into a false sense of security, particularly when creating records.
I've always used your first example to keep this clear:
#page = #go.pages.find(params[:id])
The biggest reason is because you also want to ensure this association is applied to new records, so your new/create actions will look like the following, ensuring that they are properly scoped to the parent association:
# New
#page = #go.pages.new
# Create
#page = #go.pages.create(params[:page])
Have you tried defining the default_scope a lambda? The lambda bit that defines the options get evaluated every time the scope is used.
class Page < ActiveRecord::Base
default_scope lambda do
{:conditions => "organization_id = #{Thread.current[:organization]}"}
end
# yadda yadda
end
It's essentially doing your third option, by working in tandem with your before filter magic. But it's a little more aggressive than that, kicking in on every single find used on the Page model.
If you want this behaviour for all models you could add the default_scope to ActiveRecord::Base, but you mention a few being a couple of joins away. So if you go this route, you'll have to override the default scopes in those models to address the joins.
We've been using https://github.com/penguincoder/acts_as_restricted_subdomain for the last 2 years, but it only works with Rails 2.3.
We are currently trying to upgrade (and gemmify) the plugin to work with Rails 3. I'm curious on how you worked your problem out.
You might be better of having a database per account and switching the database connection based on the subdomain.
In addition to the above link if you have a model (in your case Account) that you want to use the default database just include establish connection in the model.
class Account < ActiveRecord::Base
# Always use shared database
establish_connection "shared_#{RAILS_ENV}".to_sym
I've found the acts_as_tenant gem [github] works great for this feature. It does the scoping for you with minimal extra effort, and also makes bypassing the scoping difficult.
Here's the initial blog post about it: http://www.rollcallapp.com/blog/2011/10/03/adding-multi-tenancy-to-your-rails-app-acts-as-tenant
Related
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
I'm using Rails 4. I have a class, Cart, which needs to be accessed within my application.
I want it accessed using the factory pattern:
class CartFactory
def self.obtain_cart_for_user(user)
...
end
end
I need this approach because sometimes, I want to return an existing cart and sometimes create a new one (based upon the age of the cart, its contents, whether the products in it are still available etc).
This is easy enough.
However, I also want to make sure some other future programmer doesn't instantiate a cart directly, or fetch one by any other means, including via model associations, such as:
Cart.new(...)
user.carts.new(...)
Cart.find(id)
Cart.find_by_attribute(blah: blah)
Cart.where(...).first
Is there any way to prevent that?
Well, it's possible to make the constructor private:
private_class_method :new
And of course, you can try making the ActiveRecord query methods (.find, .where etc.) private as well. But to me that sounds like a good way to end up with erratic behaviour. If you were to go this route, make sure your app is thoroughly tested first.
Another route would be for Cart not to extend ActiveRecord::Base (which I'm assuming it does), and instead include only the parts you need, like ActiveRecord::Persistence. If you are willing to dive in deep, check out the parts that are included in the source for ActiveRecord::Base.
Edit: Still one option would be to make Cart itself private within a module that only exposes CartFactory. There's no built-in syntax for a "private class", but it's possible to achieve since Ruby classes are just regular objects. Again, no idea how well ActiveRecord would deal with that.
But lastly there is of course the question of whether you want to do this at all. In general, Ruby is not very good at protecting you from yourself. :) As expressed in the latter linked answer, documentation and trust go a long way.
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've been looking around all afternoon to try and find the dominant convention for setting up user-modifiable application-wide settings in Rails... no dice so far.
I'm working on a simple application which is intended to be used by one organization, so there's no need for a user model, there's only a single administrator. That administrator needs to have the ability to modify certain site-wide preferences, things like the logo, color scheme, tagline, etc.
What's the best practice for creating this kind of application-wide settings in Rails 3.1, and making them easily accessible to the end-user? Bonus points for any example apps you can link to.
The dominant convention to store editable app-wide settings seems to be the concept of a key-value store, backed either by ActiveRecord or other mechanisms. And, as far as I know, there are at least two nice strategies for storing your app-wide settings, depending on your requisites.
If you want a generic approach, yet extremely flexible for defining a couple of (not) scoped settings that can be in association with AR Models, you have Rails-Settings (or its cached version Rails-Settings-Cached). I haven't tried using the plugin in Rails 3.1 but it works well on 3.0. It allows you to have things like:
Settings.main_color = '#3333CC'
Settings.logo_file_name = 'images/logo.png'
Setting['preferences.color'] = :blue
In case you want a robust approach, with Single-Table-Inheritance and allowing you to perform validations in certain settings as you would with actual AR Records, you have this nice article, written by Jeff Dean, which steps you through the process. This way you scope settings by grouping them into subclasses and you can have things like:
class ApplicationSettings::PageLayout < ApplicationSetting
validates :title, :presence => true
...
def title
value
end
def title=(value)
self.value = value
end
And I guess that with some simple tuning you can even have has_many and belongs_to associations in some of your settings (like a variable-sized list of phone numbers or e-mails).
Personally I prefer the latter approach (when settings are a big issue) because it gives you more control over the settings you store and keeps your code clean and DRY, allowing you to follow the MVC pattern.
I generally set up a Properties model with some basic scaffolding in the admin section - then call the relevant Property 'field' where required, say Property.find(:field => "EARLIEST_DATE:YEAR") which would have a user settable value.
Properties might not be the best name for a database table (tend to think there's too much chance of a reserved name collision somewhere down the line) - but you get the idea. Advantage is you can set up scopes to access the values set by the user.
I wondering is there a way to represent model differently (or probably control access on fields level) depending on it's (model instance) state and controller using it.
Example:
Imagine we have an Order model with product_id, count, price and status fields.
status could be one of: :new, :confirmed, :accepted, :cancelled, :delivered and :closed.
Application can access Order from, say, two controllers CustomerOrdersController and SellerOrdersController. So, CustomerOrdersController could create and edit orders. But able to change only count field. On the other hand SellerOrdersController could edit orders. But able to change only price field. I.e. it would be great if instance of Order class that CustomerOrdersController working with have no price= method. Same for count=(product=) and SellerOrderController.
Further more set of columns permitted to edit depends on status field (probably work for some state machine).
So, the question is: how would you do this in your app?
PS
I think about some ActiveModel proxy objects for ActiveRecord instances, but do not know actually will it work or not. Consider:
class CustomerOrderProxy < ActiveModel::Base end
class SellerOrderProxy < ActiveModel::Base end
class Order < ActiveRecord::Base
def wrap_proxy(controller_user)
controller_user == CustomerOrdersController ? CustomerOrderProxy(self) : SellerOrderProxy(self)
end
end
Another approach would be to do tons of checks and params validations inside controller actions, but I do not want to. I believe in "Fat model - skinny controller" :)
PPS
I know that ruby have plenty state machine plugins, but AFAI understand they define only transitions, not method set (i.e. representation) of the object.
This sounds like simple access control. Access is granted based on the authorized user, not which controller is being used. Take a look at the cancan gem for implementing clean, declarative access control for your AR objects.
Looks like I've found appropriate solution: in Ryan Bates's screencast Dynamic attr_accessible
Update:
In Rails 3.1 update_attributes(params[:order], role) could be used. Check out rails api. Though it cannot be used to change access control according to object's state.