I'm adding strong parameters to a Rails 3.2 project. It works fine with the models I want to protect. But it seems to affect other models/controllers. For example, serialization and ransack search parameter.
It seems because strong parameters changes the class of params from HashWithIndifferentAccess to ActionController::Parameters. I wonder if there a way (or is it a good practice) to convert params back so that I can use strong parameters and don't break the current code?
Related
I am starting a project in which I am trying to have the least possible effort in the evolution, one of the points is the strong params that must be inserted in the Controller.
With that, I created a generic method that allows all parameters, followed below:
#resource_params ||= params.require(resource_name.to_sym).permit!
The problem is this: If I want some of the model's parameters not to be used (including nested parameters) is there any way to do that other than just overriding the method and adding all the allowed parameters manually?
The application is being born in Rails 6, but the idea is some solution that theoretically serves for past and future versions.
Has anyone had the same feeling of using something that really facilitates that part of the process?
This became a problem from the moment I imagined someone passing created_at and updated_at in the parameters and changing these timestamps.
Use can use except
params.require(resource_name.to_sym).except(created_at, updated_at, ...)
I have this class in my rails 4, was working perfectly fine :
class Rule < ActiveModelSerializers::Model
# Required by ActiveModelSerializers in order to seralize this object
# #return [Hash] with all the attributes accessible for serialization
def attributes
...some attributes...
end
def initialize(args = {})
super
#some_custom_variable = ...something...
end
# This method is relevant
def assign_attributes(args)
args.each { |k, v| instance_variable_set("##{k}", v) }
some_custom_variable.assign_attributes(args)
end
end
This class is called from the controller create action like so:
rule = Rule.new(permitted_params.symbolize_keys)
rule.save
The problem I have is that assign_attributes is called when executing controller code (I know this because I've put breakpoint inside), this is just happening in rails 5.
So same code, same params in the controller (generated by same tests) behaves differently. And with rails 5, the code never ends up in assign_attributes method.
My question is why does it behave so with rails 5? Why does assign_attributes gets triggered?
In Rails 4, the assign_attributes method was part of ActiveRecord, in Rails 5 it is part of ActiveModel. This change is mentioned in the Rails5 CHANGELOG which everyone reads prior to upgrading Rails.
Presumably, ActiveModelSerializers::Model includes an assign_attributes method via something in ActiveModel and something in ActiveModel thinks it is calling AM's assign_attributes rather than your accidental override.
However, the project is in a state of chaos (to be generous) so it is difficult to trace through the source:
Changes to 0.10.x maintenance:
The 0.10.x version has become a huge maintenance version. We had hoped to get it in shape for a 1.0 release, but it is clear that isn't
going to happen. Almost none of the maintainers from 0.8, 0.9, or
earlier 0.10 are still working on AMS. We'll continue to maintain
0.10.x on the 0-10-stable branch, but maintainers won't otherwise be actively developing on it.
We may choose to make a 0.11.x (
0-11-stable) release based on 0-10-stable that just removes the
deprecations.
What's happening to AMS:
There's been a lot of churn around AMS since it began back in Rails 3.2 and a lot of new libraries are around and the JSON:API spec has reached 1.0.
If there is to be a 1.0 release of AMS, it will need to address the general needs of serialization in much the way ActiveJob can be used
with different workers.
The next major release is in development. We're starting simple and avoiding, at least at the outset, all the complications in AMS
version, especially all the implicit behavior from guessing the
serializer, to the association's serializer, to the serialization
type, etc.
...
That was from six months ago and there is no useful code available on GitHub anymore, the project looks abandoned to me.
A short term fix would be to rename your assign_attributes method. A long term fix would be to replace ActiveModelSerializers altogether, preferable with something that is being actively maintained. The AMS README even offers some alternatives:
jsonapi-rb is a highly performant and modular JSON:API-only implementation. There's a vibrant community around it that has produced projects such as JSON:API Suite.
fast_jsonapi is a lightning fast JSON:API serializer for Ruby Objects from the team of Netflix.
According to both the Rails 4 and Rails 5 docs, there's nothing about whether Rails itself will use assign_attributes. Just that it's a method you can use to assign a bunch of attributes at once.
Rails 4
Allows you to set all the attributes by passing in a hash of attributes with keys matching the attribute names (which again matches the column names).
Rails 5
Allows you to set all the attributes by passing in a hash of attributes with keys matching the attribute names.
Rule.new calling or not calling Rule#assign_attributes is a quirk of the implementation. It's not documented that new will call assign_attributes so you cannot rely on when Rails will call it.
In addition, if you've overriden assign_attributes it's important that it continue to do what it's documented to do. There should be no functional difference between setting each attribute individually and setting them all at once. It appears rather than call super you've written your own mass assignment code, and added your own extra code to mirror the attributes to another object.
There's also the question of how ActiveModelSerializers::Model might be interfering.
If you want to take action when a particular argument is set, instead override the associated attribute= method.
def foo=(value)
...
end
Or use the various ActiveRecord::Callbacks such as before_validation.
Or since it appears you're trying to delegate the mass setting attributes, consider whether delegation is the answer instead.
delegate :this, :that, to: :some_custom_variable
I am still struggling to get my head around strong parameters and exactly how they work.
Firstly, which parameters are actually available by default (Edit: just confirmed that it seems, ID is, why is this?), or are they all considered unsafe until explicit permission?
Also how do I go about permitting a single parameter. I have a single ID parameter that I would like to permit that is not from a form, it is simply examples/:id
There are plenty of examples for multiple params eg
params.require(:available_time).permit(:time_start)
i understand that this statement is permitting time_start withing the available_time hash, but what if available time was not multidimensional and just included a value. How would I go about permitting it
would params[:available_time].require.permit or something work?
Some clarification would be great, thanks
If you just have a single parameter, you don't really need to use strong parameters. You can just use a regular hash when creating or updating your object:
def create
MyRecord.create(value: params[:value])
end
If you have more than one and this starts to get overwhelming, then it's better to use the multidimensional hash structure.
In Mongoid 3.0.21, how to get all model's attributes as a plain Ruby Hash?
Calling either #attributes or #raw_attributes returns Moped::BSON::Document. While it actually extends Hash, several hash method does not work as expected. Particularly #except returns unmodified self, not hash with given keys stripped off.
Update: Moped::BSON::Document properly inherits behavior of Hash. I was trying to name attributes with symbols, not strings, that's why #except didn't work. Shortly: do except('pictures'), not except(:pictures).
Hash[e.attributes]
where e is your model instance
I apologize for bumping something so old, but I wanted to leave this here for myself and all the future people who run into this same issue. I am using the Mongoid ORM for Rails, which uses Moped internally for its interaction with MongoDB.
This gem has now saved me hours and hours of manually converting things to Hash or HashWithIndifferentAccess: https://github.com/mindscratch/mongoid-indifferent-access.
Essentially it seems to have some sort of pre-return hook that automatically converts all documents coming from MongoDB to type HashWithIndifferentAccess.
Not looking for points on this. Just wanted to leave this here because it is the top Google result for this issue and it saved me from going insane.
I'm curious about people's experiences using AR's to_xml() to build non-entity fields (as in, not an attribute of the model you are serializing, but perhaps, utilizing the attributes in the process) from a controller.
to_xml seems to supply a few options for doing this.
One is by passing in references to methods on the object being acted on: during the serialization process, these methods are invoked and their results are added to the generated document. I'd like to avoid this path because some of the generated data, while depending on the object's attributes, could be outside of the scope of the model itself -- e.g., building a URL to a particular items "show" action. Plus, it requires too much forethought. I'd like to just be able to change the resultant document by tweaking the to_xml code from the controller. I don't want the hassle of having to declare a method in the object as well.
The same goes for overriding to_xml in each object.
The other two options seem to fit the bill a little better: one is by passing in procs in the serialization options that generate these fields, and the other is by passing in a block that will yielded to after serialization the objects attributes. These provide the kind of at-the-point-of-invocation customizing that I'm looking for, and in addition, their declarations bind the scope to the controller so that they have access to the same stuff that the controller does, but these methods seem critically limited: AFAICT they contain no reference to the object being serialized. They contain references to the builder object, which, sure I guess you could parse within the block/proc and find the attributes that have already been serialized and use them, but that's a harangue, or at least uneasy and suboptimal.
Correct me if I'm wrong here, but what is the point of having procs/blocks available when serializing one or more objects if you have to access to the object itself.
Anyway, please tell me how I'm wrong, because it seems like I must be overlooking something here.
Oh and yeah, I know that I could write my own view. I'm trying to leverage respond_to and to_xml to achieve minimal extra files/lines. (Though, that is what I resorted to when I couldn't figure out how to do this with AR's serialization.)
**EDIT 3.29.09 -- I just submitted a patch for this to Rails. If you're interested, show some support :) https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2373-record-sensitive-procs-for-to_xml
Actually the Proc is passed the same options hash (minus the procs option) you passed into to_xml. So you can pass in any extra objects the Proc needs to do it's job:
proc = Proc.new {|options| options[:builder].tag!('reverse-name', options[:object].name.reverse)}
object.to_xml :object => object, :procs => [ proc ]
Since you're getting the proc is getting the same options to_xml is, this is allows you to pass in whatever options you need.
Woo! My patch to handle this scenario was accepted: http://github.com/rails/rails/commit/c39151a84768397d3bb025c6e8f877eac59ebbf9 It's a part of ActiveModel now, and I'm not exactly sure what the release schedule for that is; I'm thinking Rails 3.