My DBs of production and development are somewhat in sync, so development can read images from production paths (S3).
The problem is when I delete, update or create records on development, it affects the S3 image.
I don't want this behavior to happen on development but it should happen on production.
Is there an option to turn paperclip into readonly mode? I still want to see the images from S3 (and not 404 images).
I saw the :preserve_files option which is good to protect delete. Is there an option to protect overwrite / disable upload?
Well, patchy, ugly and unsafe for future versions, but does the job for the meantime.
config/initializers/paperclip.rb
if Rails.env.development?
module Paperclip
class Attachment
def assign uploaded_file
end
def save
end
def clear(*)
end
def destroy
end
private
def post_process(*)
end
def post_process_styles(*)
end
def post_process_style(*)
end
def queue_some_for_delete(*)
end
def queue_all_for_delete
end
def after_flush_writes
end
end
end
end
Assuming you need to use production data in development, I think it would make a lot more sense to create a "User Policy" where a user can only read certain S3 resources. Then change your environment variables accordingly
https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-policies-s3.html
Then, you can handle errors in development (S3 client should fail if you try to update with read only privileges). This ensures you can't touch anything in production
For example (pseudocode),
if Rails.env.development?
// do not update
else
Model.attachment.assign()
end
Related
Let's say we have Users (who can create Doorkeeper::Applications). On the other hand our app has Admins who ideally need to check each Application that is created (and maybe do a background check on the creating User and what not) as well as its scopes. They would #approve! or #reject! the Application and only once it is approved, can the Application make calls to the API.
NOTE: #approve!, #reject!, and approved do not come with Doorkeeper, from what I know. They are hypothetical so my question is clearer.
Is this a behavior that can be achieved with Doorkeeper (or an extension)? I don't think something like this is described in the config file. If not, do you have any general steps on how this could be done?
I'm thinking that something like this could work
class Api::V1::TransactionalBaseController < Api::V1::AuthableController
before_action :doorkeeper_authorize!
before_action :check_application_status!
private
def check_application_status!
application = doorkeeper_token.application
unless application.approved?
raise Doorkeeper::Errors::ApplicationForbidden.new
end
end
end
If this is something that may help other users of the gem, I'm open to possibly opening a PR or developing an extension to achieve this.
rails g migration AddApprovedAtRejectedAtToOauthApplications approved_at:datetime rejected_at:datetime
Edit the file to reflect the correct table.
Keeping in mind that Ruby lets you modify a class from anywhere... In an initializer (or similar); from https://github.com/doorkeeper-gem/doorkeeper/issues/153:
doorkeeper_extend_dir = File.join(Rails.root, "app", "models", "doorkeeper")
Dir.glob("#{doorkeeper_extend_dir}/*.rb").each { |f| require(f) }
# app/models/doorkeeper/application.rb
module Doorkeeper
class Application < ActiveRecord::Base
def approved?
approved_at?
end
def rejected?
rejected_at?
end
def approve!
update_column(:approved_at, DateTime.now)
end
def reject!
update_column(:rejected_at, DateTime.now)
end
end
end
I have a rails 4.2 multi-tenant app using the Apartment gem which has been awesome.
Each company has their own subdomain. I'm using a custom "elevator" which looks at the full request host to determine which "Tenant" should be loaded.
When I create a new company I have an after_create hook to create the new tenant with the proper request host.
This always seems to require a restart of the server both in development and production otherwise I get a Tenant Not Found error.
It's using sqlite in development and postgres in production.
Do I really have to restart the server each time I create a new tenant? Is there an automated way to do this? Maybe just reloading the initializer will work, but I'm not sure how to do that/if that's possible?
I have been messing around with this for a month and haven't been able to find a solution that works. Please help!
initializers/apartment.rb
require 'apartment/elevators/host_hash'
config.tenant_names = lambda { Company.pluck :request_host }
Rails.application.config.middleware.use 'Apartment::Elevators::HostHash', Company.full_hosts_hash
initializers/host_hash.rb
require 'apartment/elevators/generic'
module Apartment
module Elevators
class HostHash < Generic
def initialize(app, hash = {}, processor = nil)
super app, processor
#hash = hash
end
def parse_tenant_name(request)
if request.host.split('.').first == "www"
nil
else
raise TenantNotFound,
"Cannot find tenant for host #{request.host}" unless #hash.has_key?(request.host)
#hash[request.host]
end
end
end
end
end
Company Model
after_create :create_tenant
def self.full_hosts_hash
Company.all.inject(Hash.new) do |hash, company|
hash[company.request_host] = company.request_host
hash
end
end
private
def create_tenant
Apartment::Tenant.create(request_host)
end
What ended up working
I changed the elevator configuration to get away from the HostHash one that's in the apartment gem and used a completely custom one. Mostly based off of an issue on the apartment gem github: https://github.com/influitive/apartment/issues/280
initializers/apartment.rb
Rails.application.config.middleware.use 'BaseSite::BaseElevator'
app/middleware/base_site.rb
require 'apartment/elevators/generic'
module BaseSite
class BaseElevator < Apartment::Elevators::Generic
def parse_tenant_name(request)
company = Company.find_by_request_host(request.host)
return company.request_host unless company.nil?
fail StandardError, "No website found at #{request.host} not found"
end
end
end
I think the problem could be that your host_hash.rb lives in the initializers directory. Shouldn't it be in a folder called "middleware"?, as per the Apartment gem ReadME you referenced in your comment. In that example they used app/middleware/my_custom_elevator.rb. Perhaps yours might look like app/middleware/host_hash.rb?
Right now the file is in initializers, so it's loading from there. But your apartment.rb references it by Rails.application.config.middleware.use. Just a hunch but in addition to loading it initially, it may be looking for it in a nonexistent middleware folder. I'd go ahead and create app/middleware, put the file in there instead, and see what happens. Not sure but you might need to alter require paths too.
Let us know if that helps.
I found time travelling gems like Timecop, to test time dependent features. Is it possible and/or sensible to use this in development as well?
If not: is there another time traveller gem, suitable for development? I couldn't find one.
Yes, sure you can. Here's some example code from an app I've built, where you can set a date via the admin area, and then browse the site as if it's that date, for the duration of your session:
in app/controllers/concerns/time_travel_filters.rb:
# This allows us to set a different date
# in the admin area, and use TimeCop to process each
# request as being on that different date - useful for
# testing different phases of the challenge.
module TimeTravelFilters
extend ActiveSupport::Concern
included do
if Rails.env.development? || Rails.env.staging?
around_filter :time_travel_for_request
end
end
def time_travel_for_request
time_travel
yield
time_travel_return
end
def time_travel
logger.info 'TIME TRAVEL START'
if session[:timecop_date]
Timecop.travel(session[:timecop_date])
else
Timecop.return
end
end
def time_travel_return
logger.info 'TIME TRAVEL RETURN'
Timecop.return
end
end
and then you just need to include TimeTravelFilters in your controllers that you want to use it.
You'll need to set session[:timecop_date] in order for it to take effect - I do this through a form on a page in my admin area, but you can do it however you want. Returning to the current time is as simple as deleting that session key.
I am doing a complex series of database interactions within nested transactions using ActiveRecord (Rails), involving various model.update(...), model.where(...).first_or_create(..) etc
Right before the transaction ends I'd like to report on what's actually changed and about to be written. Presumably ActiveRecord holds this information but I've not been able to work out where.
My code would be something like (semi-pseudocode)
def run options
begin
ActiveRecord::Base.transaction do |tranny|
options[:files].each do |file|
raw_data = open(file)
complex_data_stuff raw_data, options
end
report
rescue => e
"it all went horribly wrong, here's why: #{e.message}"
end
end
def report tranny
changes = {}
tranny.whatschanged?.each do |ch|
changes[ch.model.class.name] = {} unless changes[ch.model.class.name]
if changes[ch.model.class.name][ch.kind_of_update]
changes[ch.model.class.name][ch.kind_of_update] += 1
else
changes[ch.model.class.name][ch.kind_of_update] = 1
end
end
changes
end
How would I achieve something like this?
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
This is the latest version of "dirty models", which keeps track of the differences between the current object and the saved version. You access the changes via a "changes" method like in your attempt.
I added some extra stuff in one of my projects to store what was changed in the last update: this is stored in an instance variable, so is only accessable in the specific object in memory (ie you can't see it if you reload it from the database).
module ActiveRecord
class Base
attr_accessor :changed_in_last_save
before_save :set_changed_in_last_save_hash
def set_changed_in_last_save_hash
self.changed_in_last_save_hash = self.changes
end
def changed_in_last_save
self.changed_in_last_save_hash || {}
end
end
end
You definitely need ActiveModel::Dirty, you probably don't need my thing, just mentioned it as it's similar :)
I am about to begin writing a Rails application that will allow clients to have a separate subdomain for their access to our application. Thinking from a data security standpoint, it would be nice if each client's access was truly limited to their database, that way, if there is a bug in production code, they would only be able to access their own database and not that of any other clients.
I know the code behind how to do what I want, but I was wondering if there was a simpler solution that I might be missing. How would you go about securing client data so that in the event of a bug or hacker threat, their data would be less likely to be exposed?
Here is some code I use for this very problem:
application_controller.rb
before_filter :set_database
helper_method :current_website
# I use the entire domain, just change to find_by_subdomain and pass only the subdomain
def current_website
#website ||= Website.find_by_domain(request.host)
end
def set_database
current_website.use_database
end
# Bonus - add view_path
def set_paths
self.prepend_view_path current_website.view_path unless current_website.view_path.blank?
end
Website.rb
def use_database
ActiveRecord::Base.establish_connection(website_connection)
end
# Revert back to the shared database
def revert_database
ActiveRecord::Base.establish_connection(default_connection)
end
private
# Regular database.yml configuration hash
def default_connection
#default_config ||= ActiveRecord::Base.connection.instance_variable_get("#config").dup
end
# Return regular connection hash but with database name changed
# The database name is a attribute (column in the database)
def website_connection
default_connection.dup.update(:database => database_name)
end
Hope this helps!
I found a different solution that works a little easier, but makes the assumption you have a database for each Subdomain:
application_controller.rb
before_filter :subdomain_change_database
def subdomain_change_database
if request.subdomain.present? && request.subdomain != "www"
# 'SOME_PREFIX_' + is optional, but would make DBs easier to delineate
ActiveRecord::Base.establish_connection(website_connection('SOME_PREFIX_' + request.subdomain ))
end
end
# Return regular connection hash but with database name changed
# The database name is a attribute (column in the database)
def website_connection(subdomain)
default_connection.dup.update(:database => subdomain)
end
# Regular database.yml configuration hash
def default_connection
#default_config ||= ActiveRecord::Base.connection.instance_variable_get("#config").dup
end
This will switch to a database like mydb_subdomain. This is a complete replacement database option, but it makes it super easy to roll out multiple versions.
Turns out I just asked a really similar question but quite a bit further along in development - I've included three ideas for how to go about securely using a single database in there.
Off the top of my head you could run a new server instance for each subdomain using different environment.
But that won't scale very well.
However the first few google hits for multiple rails databases turn up some new suggestions. Putting together the information in those links provides this wholly untested solution for a single server instance.
You'll need to add a database entry for each subdomain in your databases.yml. Then add a before_filter to your application controller
Update! Example reloads the database configurations dynamically. Unfortunately there's no good way to make the update rails wide without messing with your server's internals. So the database configuration will have to be reloaded on every request.
This example assumes database entries in databases.yml are named after subdomains.
config/database.yml
login: &login
adapter: mysql
username: rails
password: IamAStrongPassword!
host: localhost
production:
<<: *login
database: mysite_www
subdomain1:
<<: *login
database: mysite_subdomain1
subdomain2:
<<: *login
database: mysite_subdomain2
...
app/controllers/application_controller.rb
require 'erb'
before_filter :switch_db_connection
def switch_db_connection
subdomain = request.subdomains.first
ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(Rails.configuration.database_configuration_file)).result)
ActiveRecord::Base.establish_connection("mysite_#{subdomain}")
end
As I said it's completely untested. But I don't foresee any major problems. If it doesn't work hopefully it puts you on the right track.