delete_all is useful, but I never want to see it called on the same line with a table name. I'd like to disable things like TableName.destroy_all in both console and code.
One interesting issue happened earlier this month:
Application.destroy_all was called on a model instead of applications.destroy_all
(the model has_many applications)
For somebody new to ROR, it looks very similar, but the results were disastrous.
I'm open to some form of lint/code style tool, but that really wouldn't catch it in the console scenario. (Plus, I haven't been able to get rubo-cop to do something like this yet)
Basically, I'm asking for a way to make the console and codebase more secure so that newer developers can't inadvertantly delete everything in a table.
I'm not entirely clear on what you are trying to accomplish, but you could try overriding the method in your ApplicationModel with something like this (assuming Rails 5 or greater, or otherwise a root model in existence).
class ApplicationModel < ActiveRecord::Base
def self.destroy_all(*args)
raise('Cannot destroy all records of a model this way. Did you mean to delete a subset of records instead?')
end
end
Possibly make this method private if you'd like it even harder to run...
def self.destroy_all(*args)
raise('Cannot destroy all records of a model this way. Did you mean to delete a subset of records instead?')
end
private_class_method :destroy_all
You could get fancy and allow this to be bypassed with a special argument that you check for, but give this a try and see how it goes.
Related
I'm in the process of updating a website I made almost 2 years ago. It was my first real website and I made some mistakes (some more serious that others).
What apparently is one of my biggest is making database calls from the view.
I'm pretty damn sure there is a better way to do this:
Use Case:
Someone fills out a form for a new subject, populating the Subject table, and they have been marked "enrolled", Subject.enrolled = 1
Based on that, I now need to create a record in 5 other tables (such as Baseline)
Downhill from here, here is my method
Determine if the record exist based on subject_id from Subject (sub)
<$ if Baseline.where(subject_id: sub.subject_id).first != nil $>
If it does not exist, create the record, (otherwise display the link)
<%= Baseline.create(subject_id: sub.subject_id) %>
This all happens in the view, and creates a front-end table with links to each record in the process. So I'm creating records based on for-loop logic...
Question:
So I'm looking for direction. I don't want to guess how to do this - I'm pretty sure the model/controller should do this - I want to learn how to do it correctly. How do I create records automatically, based on a value in a table?
Thank you for your time.
Not quite sure how your domain and code looks like, but to answer this question: 'How do I create records automatically, based on a value in a table?', it seems that you could use ActiveRecord callbacks, like this:
class Subject < ActiveRecord::Base
after_commit :create_baseline_if_enrolled, on: [:create, :update]
private
def create_baseline_if_enrolled
return unless enrolled?
# enrolled? == true, you may create these models here
end
end
To answer your question:
It depends :) This is just one possible solution. Another one would be to put such a custom logic in your SubjectsController and call it directly from the #create, #update methods. Both approaches have pros and cons. For example, abusing callbacks (anywhere) makes code less readable and harder to debug. On the other hand, putting such logic in controllers puts a burden on you that you have to remember about calling it if you happen to be editing subjects in other places (but is more explicit). Whichever way you choose, remember not to make your classes too fat, for example try to use service object pattern to separate such custom logic as soon as you feel like it is getting out of hand. :) And don't forget about tests - when things go wrong, tests make refactoring easier.
I have a little problem. I found it really hard to create a title for this, I hope I can explain it better:
One of our clients demanded that in every instance of "item_number", when it's from the Product model, the app will have to show it with 5 leading zeroes.
The "item_number" proprety in that model is in fact an integer on the database.
Our first approach would be just change in the model the value:
def item_number
item_number.to_s.rjust(5, '0')
end
Of course we get a:
SystemStackError at /
stack level too deep
And I'm pretty sure this will mess up when creating/updating records, but it doesn't matter, it doesn't work anyways.
The solution would be simple, just create:
def item_number_with_leading_zeroes
item_number.to_s.rjust(5, '0')
end
And replace item_number with it on our views.
But, we have over 5.000 usages of this, and some of them were not from this model (and we only need to replace the item_number on this model) and some of them are inside Iterators that we coded not using the parent object name (so I may not know if it's from this model or not), others are from custom form inputs, we can't just "replace all".
I would have to manually check every single instance of "item_number" and see if it's needed to change it to that new method or not. And I can still make mistakes and impact a lot of stuff.
I only need to change it on the views, I thought about a way to get it working with the helpers but that would still mean I would have to change it case-by-case.
There is another option to fix it, updating all records on the database to string with the zeroes and making a before_save option from now on, but we would like a rails solution first, before going that way (leaving the DB as is right now).
I'm out of ideas, I don't know if there is a solution, I thought about asking here first hoping someone can share some thought.
Is there any way I can do this without having to manually check the whole application?
Ruby 2, Rails 4.0
Thank you!
Try using read_attribute to avoid the recursion:
class Product < ActiveRecord::Base
...
def item_number
read_attribute(:item_number).to_s.rjust(5, '0')
end
...
end
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 ran into various problems with various sites over the years with users putting spaces at the start/end of string and text fields. Sometimes these cause formatting/layout problems, sometimes they cause searching problems (ie search order looking wrong even though it isn't really), sometimes they actually crash the app.
I thought it would be useful, rather than putting in a bunch of before_save callbacks as i have done in the past, to add some functionality to ActiveRecord to automatically call .strip on any string/text fields before saving, unless i tell it not to, eg with do_not_strip :field_x, :field_y or something similar at the top of the class definition.
Before i go and figure out how to do this, has anyone seen a nicer solution? Just to be clear, i already know that i can do this:
before_save :strip_text_fields
def strip_text_fields
self.field_x.strip!
self.field_y.strip!
end
but i'm looking for a nicer way.
cheers, max
Here's a handy module that you could drop into lib and include in your models. It doesn't have the exceptions that you mentioned, but it looks for a strip! method which might be good enough. You could add the exceptions feature fairly easily, if needed.
# lib/attribute_stripping.rb
module AttributeStripping
def self.included(context)
context.send :before_validation, :strip_whitespace_from_attributes
end
def strip_whitespace_from_attributes
attributes.each_value { |v| v.strip! if v.respond_to? :strip! }
end
end
Use like this:
class MyModel < ActiveRecord::Base
include AttributeStripping
# ...
end
UPDATE (9/10/2013):
Revisiting this answer a couple of years later, I see how the winds have changed. There's a cleaner way to do it now. Create a module like this:
module AttributeStripper
def self.before_validation(model)
model.attributes.each_value { |v| v.strip! if v.respond_to? :strip! }
true
end
end
and set its method to be invoked at the right time in your model:
class MyModel < ActiveRecord::Base
before_validation AttributeStripper
# ...
end
This module is easier to test since it's not a mixin.
I have dealt with these sort of data integrity issues in various applications.
I used to manipulate the input like that.
But now, the best advice I have actually seen and followed is to store whatever the user types.
Then do post-processing on the backend to do the strip.
Create additional database fields (destripped) if you really want it in the database model table.
The main reason for this is one (primary) thing - when users want to revisit their data, i.e. edit, they're usually gonna expect to see what they typed in. A secondary reason is that you will avoid the possibility that your strip doesn't work right and either mangles the data or actually throw an error.
I've written a plugin for this purpose some time ago. I haven't tried it in a while and it doesn't have tests - so no guaranties that it still works. The upside would be a clean model:
class Story < ActiveRecord::Base
strip_strings :title, :abstract, :text
end
To preserve data integrity, I need to prevent some models from being modified after certain events. For example, a product shouldn't be allowed to be written off after it has been sold.
I've always implemented this in the controller, like so (pseudo-ish code):
def ProductsController < ApplicationController
before_filter require_product_not_sold, :only => [ :write_off ]
private
def require_product_not_sold
if #product.sold?
redirect_to #product, :error => "You can't write off a product that has been sold"
end
end
end
It just struck me that I could also do this in the model. Something like this:
def Product < ActiveRecord::Base
before_update :require_product_not_sold
private
def require_product_not_sold
if self.written_off_changed?
# Add an error, fail validation etc. Prevent the model from saving
end
end
end
Also consider that there may be several different events that require that a product has not been sold to take place.
I like the controller approach - you can set meaningful flash messages rather than adding validation errors. But it feels like this code should be in the model (eg if I wanted to use the model outside of my Rails app).
Am I doing it wrong?
What are the advantages of handling this in my model?
What are the disadvantages of handling this in my model?
If I handle it in the model, should I really be using validates rather than a callback? What's the cleanest way to handle it?
Thanks for your ideas :)
It seems like you already have this one covered, based on your question. Ideally a model should know how to guard its state, as data objects are typically designed with portability in mind (even when they'll never be used that way).
But in this case you want to prevent an action before the user even has access to the model. Using a model validation in this case means you're too late and the user has already gone farther than he should by having access to and attempting to write off a product which should never have been accessible based on its sold status.
So I guess the ideal answer is "both." The model "should" know how to protect itself, as a backup and in case it's ever used externally.
However in the real world we have different priorities and constraints, so I'd suggest implementing the change you listed if feasible, or saving it for the next project if not.
As far as using a model callback versus a validation, I think that's a trickier question but I'll go with a validation because you'd likely want to present a message to the user and validation is built for exactly that use (I'd consider this more of a friendly and expected user error than a hostile or security-related one which you might handle differently).
Is that along the lines of what you've been considering?