I'm wondering, if i have a model where all values can be set through a form, do i still need to use attr_accessible ?
The important thing that comes to my mind is the id field (or maybe even the created_at, updated_at) fields.
Should i always whitelist the accessible fields if there is a form around ?
You should think if in the future you will add some importaint data and you will forget to protect it. Using attr_accessible it will be protected by default.
If there are any associations, such as user has_many :roles, :through => :authorization in User model there aren't any data about this association, but hacker can mass_assign roles_attributes. So he will change data in authorizations table through the hole in User model.
Also he will availible to manupalate with simple has_many :dollars associations passing dollar_ids=[1,2,3,4,5,6,7...] via form.
You can checkout also Ryan's screencast about mass assignment
http://railscasts.com/episodes/26-hackers-love-mass-assignment
no, id is explicitly excluded. in fact, including it in attr_accessible still won't let people overwrite it.
but it's still best practice to use attr_accessible, even if you intend to allow people to update everything
Related
I have a situation where I need to collect data from a user, save the data, and later be able to go back and view the data. In this case it is a survey. Normally I would create a model and put in some validations and it would be pretty straightforward.
But in this case the questions change so frequently that if I did that I might find myself writing new migrations and performing all kinds of maintenance constantly. And since all we really want is the data itself, it occurred to me that I should just make a model that has an id field and a form_data field and just stick whatever the params are into that form_data field. That way all I have to do is change the view and not mess with the model at all.
But later if I wanted to edit the data in the survey, how would I populate the form fields? The goal is to only change the view whenever my boss wants to add new questions.
P.S. I would prefer not to use Surveyor for this task since I'd like to be able to do some things that aren't easily don ein Surveyor from what I can tell.
So this is what it might look like
SurveySubmissions
id : integer
form_data: string
SurveySubmissions.find(1).form_data
"name":"Kevin"
"gender":"Male"
"favorite_color":"blue"
If you are using PostgreSQL you can take a look at Hstore. A nice starting point is this Railscast: http://railscasts.com/episodes/345-hstore (pro episode, so it requires payment).
It allows you to store all question/answer pairs in single database column.
You can also serialize your data into single database column as json/yml and use virtual attributes on your model to make it easy to populate form fields (Railscast episode about virtual attributes: http://railscasts.com/episodes/16-virtual-attributes-revised, there is also old episode for free)
Another solution (and my favorite) is to use MongoDB + Mongoid and its dynamic attributes.
It can be the best solution if you didn't start coding yet.
This might be helpful for you :
User Model
has_many :surveys
Survey Model
belongs_to :user
has_many :questions
Submissions Model
has_many :questions
belongs_to :user
belongs_to :survey
Question Model
has_many :answers
polymorphic => belongs_to "survey and submissions"
Answer Model
belongs_to :submission
belongs_to :question
and on top of this u can build relations and other stuff you require as per your requirements
I think the best choice is to save the form fields into the database and then use javascript to repopulate the form when editing. So I pull all the field values into a variable and then use JQuery.each to populate the fields.
surveySubmissionData = {"incident_id":"2013BaconSupreme",
"_confirmed":"No",
"_date_began":"2/26/2013"}
// Put some code here that loops through the surveySubmissionData
// and populates the form.
jQuery.each(surveySubmissionData, function(i, val) {
$("#" + i).val(val);
});
When I create a scaffold and I need to have a belongs_to relation to another model, I add a field called model_id (replacing model with that model's name):
rails generate scaffold Grade user_id:integer subject_id:integer letter:string
Then in the above Grade model, I might add:
belongs_to :user
belongs_to :subject
Rails automatically adds user_id and subject_id to the list of attr_accessible fields. Do I do any harm by also adding :user and :subject to the list of attr_accessible fields so that I can mass assign using those as well?
attr_accessible is intended to protect against mass-assignment attacks that come from data that is externally sent to your application. In most cases you're probably doing things like this in your create & update actions:
#model = Model.new(params[:model])
or
#model.update_attributes(params[:model])
You should ask yourself why you'd have one form that uses subject_id and another that uses subject. The only real harm here is inconsistency, which can actually be pretty detrimental to large projects. If you follow the convention that all forms will use the actual database column (subject_id), then you'll save yourself some headache in the future when you can't remember out why you did it two different ways.
If you're updating attributes through the console, you can either use update_attributes(params[:model], without_protection: true) or a gem I wrote called sudo_attributes which lets you do sudo_update_attributes(params[:model]).
I think it doesn't hurt you, but will bring a kind of mess in your code
I have a Rails application, with two models: SalesTransactions and PurchaseOrders.
In the PurchaseOrders model, new entries are registered using 'purchase_order_number' as the key field. I use the create method of the model to search if that 'purchase_order_number' has been previously registered, and if so, reuse that record and use its id in the SalesTransaction record. If that name wasn't already registered, I go ahead and perform the create, and then use the new PurchaseOrder record id in the SalesTransaction (the foreign_id linking to the associated PO).
Note that I don't have the existing PurchaseOrder record id until I've done a look-up in the create method (so this is not a question of 'how do I update a record using 'accepts_nested_attributes_for'?', I can do that once I have the id).
In some situations, my application records a new SalesTransaction, and creates a new PurchaseOrder at the same time. It uses accepts_nested_attributes_for to create the PurchaseOrder record.
The problem appears to be that when using 'accepts_nested_attributes_for', create is not called and so my model does not have the opportunity to intercept the create, and look-up if the 'purchase_order_number' has already been registered and handle that case.
I'd appreciate suggestions as to how to intercept 'accepts_nested_attributes_for' creations to allow some pre-processing (i.e. look up if the PurchaseOrder record with that number already exists, and if so, use it).
Not all Sales have a PurchaseOrder, so the PurchaseOrder record is optional within a SalesTransaction.
(I've seen a kludge involving :reject_if, but that does not allow me to add the existing record id as the foreign_id within the parent record.)
Thanks.
You could use validate and save callbacks to do what you need.
Assuming the setup:
class SalesTransaction < ActiveRecord::Base
belongs_to :purchase_order, :foreign_key => "po_purchase_order_no",
:primary_key => "purchase_order_no"
accepts_nested_attributes_for :purchase_order
end
class PurchaseOrder < ActiveRecord::Base
has_many :sales_transactions, :foreign_key => "po_purchase_order_no",
:primary_key => "purchase_order_no"
before_validation :check_for_exisitng_po # maybe only on create?
accepts_nested_attributes_for :sales_transactions
private
def check_for_exisitng_po
existing_po = PurchaseOrder.find_by_purchase_order_no(self.purchase_order_no)
if existing_po
self.id = existing_po.id
self.reload # don't like this, also will overwrite incoming attrs
#new_record = false # tell AR this is not a new record
end
true
end
end
This should give back full use of accepts_nested_attributes_for again.
gist w/tests
Two ideas: Have you taken a look at association callbacks? Perhaps you can "intercept" accepts_nested_attributes_for at this level, using :before_add to check if it is already in the DB before creating a new record.
The other idea is to post-process instead. In an after_save/update you could look up all of the records with the name (that ought to be unique), and if there's more than one then merge them.
I was going to write a before_save function, but you say this:
It uses accepts_nested_attributes_for to create the PurchaseOrder record.
So in the SalesTransaction process flow, why look it up at all? You should just get the next one available... there shouldn't be a reason to search for something that didn't exist until NOW.
OK, I've left this question out there for a while, and offered a bounty, but I've not got the answer I'm looking for (though I certainly appreciate folk trying to help).
I'm concluding that I wasn't missing some trick and, at the time of writing, there isn't a neat solution, only work-arounds.
As such, I'm going to rewrite my App to avoid using accept_nested_attributes_for, and post the SalesTransaction and the PurchaseOrder records separately, so the create code can be applied in both cases.
A shame, as accept_nested... is pretty cool otherwise, but it's not complete enough in this case.
I still love Rails ;-)
I'm currently using the mass assignment security baked into rails 3 to scope what level of users can update about their model. For example this code allows me to protect attributes based on the user level.
class Customer
attr_accessor :name, :credit_rating
attr_accessible :name
attr_accessible :name, :credit_rating, :as => :admin
end
I would like to be able to use this same idea for which attributes appear when I do a find. For example I would like to be able to say
Customer.all.as(:admin)
and get back the credit rating. Compare this to doing
Customer.all
and getting back all the attributes except the credit_rating
Is this something rails supports and I've missed?
attr_accessible is used to filter incoming attributes on mass assignment. This is a convenience method created so that a developer does not need to manually clean the incoming hash of params, something he does not control.
When displaying information a developer is in full control of what he/she desires to show, so there seems to be no reason to limit the read functionality.
However, rails allows you to "select" the attributes you desire in a query: see http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields
You could easily create a scope with the name admin that would limit the selected values.
If you do not desire to have the full models, but only the values, you could use the generated sql. e:g.
ActiveRecord::Base.connection.select_values(Customer.select('name').to_sql)
In the User model I have two accepts_nested_attributes_for: :details (which is has_one association) and :membership_orders (has_many).
For :details I have:
attr_accessible :details_attributes
But for the membership_orders I can't have so simple accessor, because I want to protect it from the normal user, but make it accessible for the admin.
It's possible to do with attribute-permissions plugin (github.com/Fingertips/attribute-permissions/tree/master), but I think it's not the finest solution.
Can you tell me how I can add special expression for the attr_accessible, or maybe filter out those attributes using before_validation (or what-else).
You can read about this problem by this link: blog.smartlogicsolutions.com/2009/02/24/rails-23-nested-object-forms-im-not-crazy-about-them/
Thanks.
You could certainly filter them out before validation. Another option is to switch to attr_protected and specify the fields you want protected instead of the ones you want open. That could be a simple switch or a more involved one, depending on the size of your models.