ActiveModel form using fields_for in Rails 5 - ruby-on-rails

I have an ActiveModel "form object" which "has many" patients (another ActiveModel "form object"). The idea being the user can fill in the form and add as many patients.
- #form.patients.each do |patient|
= form.fields_for 'patients[]', patient do |patient_form|
This worked fine in Rails 4 and as far as I remember inputs where named something like patients[][name].
This was an Array in params such as { patients: [ {name: 'foo'} ] }.
However it seems this may have changed with Rails 5, looking at the source it seems if name ends in [] and there is no index given it will try and insert an id.
Giving an input name such as patients[1][name].
However the form object (ActiveModel) has no #id method and I get an NoMethodError. And even if it did this would give a params has which would be a Hash such as { patients: { '1' => { name: 'foo' } } }.
If I just do form.fields_for 'patients', patient all input have the same name and thus overwrite each other in params.

One solution is to add a "fake" #id method to the "form object":
# form object (ActiveModel)
def id
SecureRandomn.hex(10)
end
This will give inputs named as such: patients[f0858de30df23c3e2305][name].
Then in the controller I can convert the params hash ({ id => attributes, id => attributes, ... }) to an array of attributes ([attributes, attributes, ...]):
params[:patients].values
While I don't mind 'fixing' the params too much adding a useless #id method to the form seems too much.

Related

Rails - permit a param of unknown type (string, hash, array, or fixnum)

My model has a custom_fields column that serializes an array of hashes. Each of these hashes has a value attribute, which can be a hash, array, string, or fixnum. What could I do to permit this value attribute regardless of its type?
My current permitted params line looks something like:
params.require(:model_name).permit([
:field_one,
:field_two,
custom_fields: [:value]
])
Is there any way I can modify this to accept when value is an unknown type?
What you want can probably be done, but will take some work. Your best bet is this post: http://blog.trackets.com/2013/08/17/strong-parameters-by-example.html
This is not my work, but I have used the technique they outline in an app I wrote. The part you are looking for is at the end:
params = ActionController::Parameters.new(user: { username: "john", data: { foo: "bar" } })
# let's assume we can't do this because the data hash can contain any kind of data
params.require(:user).permit(:username, data: [ :foo ])
# we need to use the power of ruby to do this "by hand"
params.require(:user).permit(:username).tap do |whitelisted|
whitelisted[:data] = params[:user][:data]
end
# Unpermitted parameters: data
# => { "username" => "john", "data" => {"foo"=>"bar"} }
That blog post helped me understand params and I still refer to it when I need to brush up on the details.

Updating Attribute through reference

I am wondering if it's possible to reference an object's attribute.
The object User have attribute first_name, so normally if we want to update the first name attribute we do:
user0 = User.new()
user0.update_attribute(:first_name, "joe")
now my question is can I update the :first_name attribute through another variable/symbol like so:
user0.update_attribute(:fname_ref, "jack")
user0.first_name #=> has the value jack
I was looking for variable reference like in Perl, but came up nothing in Ruby.
---------- EDIT
I am in the middle of doing the lynda ruby on rails tutorial, and in the middle of creating a module to adjust positions of items in a table.
unfortunately when I first started I named my tables columns differently
pages.page_position, subjects.subject_position, sections.section_position
now the module PositionMover is to be used accross three models, so now
I have a problem since the attributes names are different for each model
so I thought no worry I'll just create a pointer / refference for each model
:position = :pages_position , :position = :subjects_position , :position = :section_position
hence the question , if its even possible to do it.
if its not possible , any suggestion what should I do , so the module can
be used accross three different models , with different attribute names.
sorry I am a newbie.
Symbols are not like variables, they are actually a object type, like String or Fixnum so you can have a variable that is of type symbol.
I think this is what you are looking for:
attribute = :first_name
user0.update_attribute(attribute, "jack")
user0.first_name #=> has the value jack
Update: If you have a String and need to convert to a symbol, (I'm not sure if you need this for update_attribute)
foo = "string"
foo.to_sym #=> :string
Use the alias_attribute . Define into each model like :
Page model
alias_attribute :position , :pages_position
Subject Model
alias_attribute :position , :subjects_position
Section Model
alias_attribute :position , :section_position
And use (Model.position = values) with each model . Hope Its solution of your problem .
You can also use send docs and use symbols or strings to reference the attribute's methods. send can be incredibly useful since it enables you to choose the method that you'll be invoking at runtime.
Eg
user.first_name = "Jack" # set first_name to Jack
# Note: method "first_name=" (a settor) is being used
attribute = "first_name"
user.send attribute + "=", "Jack" # set first_name to Jack
# The string "first_name=" is coerced into
# a symbol
attribute = :first_name
val = user.send attribute # => returns "Jack"
see the definition of update_attribute in the ActiveRecord::Persistence module on github:
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
send("#{name}=", value)
save(:validate => false)
end
this leads me to believe you could add the following to your model to achieve that behavior:
alias_method :fname_ref= :first_name=
I'd be interested to know why you want to do that as #Andrew Marshall asked.

Mongoid - Updating Nested Attributes

From the mongoid docs:
Consider a member that has a number of posts:
class Member include Mongoid::Document has_many :posts
accepts_nested_attributes_for :posts end
You can now set or update attributes on an associated post model
through the attribute hash.
For each hash that does not have an id key a new record will be
instantiated, unless the hash also contains a _destroy key that
evaluates to true.
params = { member: { name: "joe", posts_attributes: [
{ title: "Kari, the awesome Ruby documentation browser!" },
{ title: "The egalitarian assumption..." },
{ title: "", _destroy: "1" } # this will be ignored ] }}
member = Member.create(params['member']) member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation
browser!' member.posts.second.title # => 'The egalitarian
assumption...'
Is there a way to update nested attributes instead of creating them?
It relies on the nested documents having IDs.
In a Rails form, for instance, the corresponding attributes fields (in your case, posts_attributes) will be passed as part of the form. Rails then does an update for elements with an ID, and a create for those without an ID.

Nested attributes in hash representation of a record

I've given up hours of my day trying to accomplish this simple thing in Rails 3.1 with no luck. I've got some models nested 2 levels deep and associated many-to-one with belongs_to/foreign key, like:
TopLevelModel:
MiddleLevelModel:
BottomLevelModel
I am eagerly loading the whole hierarchy in my queries like so:
#model = TopLevelModel.find(1, :include => {:middle_level_children => :bottom_level_children})
The JSON serializer works fine for serializing the nested hierarchy (using the :include option), but this isn't enough for my purposes and I need a (ruby) hash representation of the record's attributes. #model.attributes() would be perfect but it neglects my relations. Is there a way to get a nested hash representation using this method (I read the documentation thoroughly and suspect not, but maybe there's some exotic option I don't know about). To be clear, the representation I am looking for would be:
{
:attribute_1 => 'some attribute', #an attribute of top level model
#...
:middle_level_children: => [{ # type 'MiddleLevelModel'
:attr_1 => 'some attribute of middle level model',
# ...
:bottom_level_children => [{ #type 'BottomLevelModel'
:attr => 'some attribute of bottom level model'
}]
}]
}
This seems like an incredibly simple (and, I would think, common) need, but I've had no luck.
Why can't you iterate through all your child relationships and print all the attributes for each instance of them?
Might be a little hokey but give Hash.from_xml a whirl.
Use the object's to_xml method to serialize with associations and then deserialize with the Hash.from_xml class method.
xml = #model_instance.to_xml(:include=>:middle_level_children)
nested_hash = Hash.from_xml(xml)

What attributes are updated in model

Is there a way I can know what attributes are updated in my model? I only want to do a validation on a user when a certain attribute was posted. This would actually be the hash that is sent via the params
eg:
#user.update_attributes(params[:user])
Thanks.
This is available within the ActiveRecord::Dirty module, which is included by default.
The changed method will give you a list of attributes with unsaved changes. The changes method will give you a hash of unsaved changes, where the keys are the attribute names and the values are an array consisting of the original and new value.
For example:
#user.changed # => ['name', 'age']
#user.changes # => { 'name' => ['Bill', 'John'], 'age' => [18, 21] }

Resources