I have a fields_for tag, where I specify the prefix (lets say for some good reasons), and this is supposed to represent a one-to-one relationship.
I am trying to represent a relationship
widget has_many thingamagigs
thingamagig has_one whatchamacallit
The field_for code is:
fields_for "widgt[thingamagigs_attributes][][whatchamacallit_attributes]", thingamagig.whatchamacallit do |x|
which generates names (wrongly):
widget[thingamagigs_attributes][][whatchamacallit_attributes][][value]
The better solution would be
t.fields_for :whatchamacallit do |x|
where t = fields_for the thingamagig... However if I do that, the following names are generated
widgt[thingamagigs_attributes][whatchamacallit_attributes][]
which is completely wrong as all other fields for a thingamagig is...
widgt[thingamagigs_attributes][][name]
So in all cases I am screwed. The original field_for using a string cannot be used with accepts_nested_attributes_for :whatchamacallit since whatchamacallit is a singular relationship and an object is expected not an array. The second fields_for will simply not work because rails cannot parse the params object correctly. Is there a way to tell the first forms_for to not add the [] after [whatchamacallit_attributes] in all field names?
My current solution is to augment the model with
def whatchamacallit_attributes=(whatchamacallit_attributes)
assign_nested_attributes_for_one_to_one_association(:whatchamacallit, whatchamacallit_attributes[0])
end
Which will work even with the broken form fields. However this feels extremely hacky, does anyone have a solution?
def whatchamacallit_attributes=(whatchamacallit_attributes)
assign_nested_attributes_for_one_to_one_association(:whatchamacallit, whatchamacallit_attributes[0])
end
Looks like I have to stick with this solution since nothing else was offered.
Related
Considering Students who can study various things, I'm storing those in a jsonb column referencing a Studies table. Indexing the studies isn't really important (for now) and I prefer to avoid a relationship table.
Therefore: add_column :students, :studies, :jsonb, default: []
And in my simple form (in slim):
= simple_form_for #student do |f|
= f.input :studies, as: :check_boxes, collection: Study.all, label_method: :name
This works stupendously well considering the brevity and the simplicity of it. Except for one small detail: the form doesn't check previously saved studies as their IDs are stored as strings in the jsonb array ["", "2", "12"] and the form apparently requires integers.
I resorted to add a studies' value function in the Student model, but it seems sooo overkill (also the .reject(&:zero?) to remove the empty array value):
def studies=(array)
# transform strings to integers and remove leading empty value
super(array.map(&:to_i).reject(&:zero?))
end
Is there a better way?
I would say the better way is just using the relationship table. Overriding the assignment method on a model is generally not the right approach.
JSONB is nice, gives flexibility, and can be even queried nicely, but unless you have a really strong reason to go with it in this case, you should probably stick to has_many :through... association.
Either way, depending on how you wired everything, maybe instead of overriding the assignment method you would be better by putting your logic in action filters or somewhere where you do model validation...
I would like to know the use cases for nested attributes. The pros and cons of using vs. not using it.
I have a model that has a lot of has_many associations. Example:
class Post
has_many :visitors
has_many :pageviews
has_one :metric
end
Although I like the idea of just sending one request and having all of those things created with the correct associations, I don't like the idea that all the creation of the visitors, pageviews and metric are in the PostsController. I very much like the separation of concerns. Is there any clear rule that I should follow when dealing with nested attributes?
Thank you.
In data-modelling we sometimes split up stuff over different tables/models, and imho nested models/nested forms are mostly used where the nested model has no reason to exist without the parent and vice versa. E.g. a person with their addresses: when creating a person we immediately need to add their address because (for instance in a delivery situation) a person without an address makes no sense.
Nested forms, where we can edit parent/child models as a whole, was popular and imho still has its benefit/place in some very specific situations (like the aforementioned example).
But in most cases, where the creation of the parent is not dependant on the child, I rather prefer to use ajax calls. I present all the information on a page, but when adding a child I make sure it is immediately saved and stored in the database (using their own controller --while visiting the parent's show page for instance).
I believe in most cases that the default/standard mega form is not the best UI/UX solution, and I believe that should be the main driver how to build your pages.
Coming back to your example: these should never be shown in one huge form, nobody is going to edit a post, and the stats, right? Statistics are collected and shown, but hopefully never "created". Normally one tracks actual pageviews, visitors ...
I think you're making a leap you don't need to.
I don't like the idea that all the creation of the visitors, pageviews and metric are in the PostsController.
They shouldn't be and don't have to be. In fact, they never are by default.
Run rails routes in your console and you'll see each of those has_many models have their own controllers and views.
It is very rare I have a web app with a model that doesn't have and use its own controller.
A route like /posts/:id/visitors should point to visitors#index, not something like posts#visitors
If you're putting everything into one controller, I'd argue you aren't actually nesting, you're expanding.
And I'd argue the point of relational databases is to have relationships, so limiting your relationships seems kinda self-limiting.
I don't think you have really grasped what nested attributes is used for in the first place. Its used when you need to CRUD a resource and its children in a single request.
Its use is really dictated by the user experience requirements. Sometimes you actually need a single form like this very common order form example:
class Order
has_many :line_items
accepts_nested_attributes_for :line_items
end
class LineItem
belongs_to :product
end
<%= form_for(#order) do |f| %>
<%= f.fields_for(:line_items) do |ff| %>
<%= ff.number_field :quantity %>
<%= ff.collection_select :product_id, Product.all, :id, :name %>
<% end %>
<% end %>
class OrdersController < ApplicationController
# ...
def update
#order = Order.find(params[:id])
if #order.update(order_params)
# ..
else
# ...
end
end
private
def order_params
params.require(:order)
.permit(:line_items_attributes: [:quantity, :product_id, :_destroy])
end
end
This is just that common checkout form where users can change the number of products in their cart. It lets the users manipulate multiple records at once in a plain old synchronous form.
That said nested attributes is probably one of the most misused components of Rails though and everything beyond 1 level of nesting usually ends up in a hideous mess. Its also a very common misconception that it should be used to assign associations which should in most cases just by done by adding selects or checkboxes that point to the _id or _ids attributes created by the associations.
If you are using it just to mosh everything into a single controller because "I don't want to have too many classes. Waaah" then yes its a huge anti-pattern.
The alternative really is using AJAX to let the user CRUD child records without reloading the page.
I would say that your example is not a good candidate for nested attributes. Are any of those associations actually even created by the user?
Lets say I have a working form that looks like the following
=form_for #survey do |f|
=f.text_field :name
=f.fields_for :questions do |question_fields|
=question_fields.text_field :question_text
=question_fields.fields_for :answers do |answer_fields|
=answer_fields.text_field :answer_text
Because different parts of the form can be added and updated by different users I need a way to get the user_id into each model before it is saved. I realize it is not mvc compliant to be able to access current_user inside the model, that being said I am left without a solution.
If I was only saving one object it would be simple enough to assign the current_user.id to the object in the controller, but given the deeply nested nature of this form that starts to look like an ugly solution.
Is there an expert/railsy way to handle this?
Rails 3.2, devise
Can't each of the objects simply steal the user_id from their "parent" relationship? This is a common pattern:
class Answer < ActiveRecord::Base
before_validation :assign_user_id
protected
def assign_user_id
# Don't attempt if the question is not defined,
# or the user_id field is already populated.
return unless (self.question or self.user)
self.user_id = self.question.user_id
end
end
This involves a bit of additional database activity to resolve the answer for each question, as creating it in a scope is not sufficient, but it makes it pretty much fool-proof.
What you probably want to do is stuff in the user_id parameter when creating each record. This means your create call needs to merge in a :user_id key where required. The nested helper doesn't do this by default, though, so if you're using that you may just leave it up to the assign method.
Hey,
Not a Rails noob but this has stumped me.
With has many through associations in Rails. When I mass assign wines to a winebar through a winelist association (or through) table with something like this.
class WineBarController
def update
#winebar = WineBar.find(params[:id])
#winebar.wines = Wine.find(params[:wine_bar][:wine_ids].split(",")) // Mass assign wines.
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
This will delete every winelist row associated with that winebar. Then it finds all of the wines in wine_ids, which we presume is a comma separated string of wine ids. Then it inserts back into the winelist a new association. This would be expensive, but fine if the destroyed association rows didn't have metadata such as the individual wine bar's price per glass and bottle.
Is there a way to have it not blow everything away, just do an enumerable comparison of the arrays and insert delete whatever changes. I feel like that's something rails does and I'm just missing something obvious.
Thanks.
Your problem looks like it's with your first statement in the update method - you're creating a new wine bar record, instead of loading an existing record and updating it. That's why when you examine the record, there's nothing showing of the relationship. Rails is smart enough not to drop/create every record on the list, so don't worry about that.
If you're using the standard rails setup for your forms:
<% form_for #wine_bar do |f| %>
Then you can call your update like this:
class WineBarController
def update
#winebar = WineBar.find(params[:id])
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
You don't need to explicitly update your record with params[:wine_bar][:wine_ids], because when you updated it with params[:wine_bar], the wine_ids were included as part of that. I hope this helps!
UPDATE: You mentioned that this doesn't work because of how the forms are setup, but you can fix it easily. In your form, you'll want to rename the input field from wine_bar[wine_ids] to wine_bar[wine_ids_string]. Then you just need to create the accessors in your model, like so:
class WineBar < ActiveRecord::Base
def wine_ids_string
wines.map(&:id).join(',')
end
def wine_ids_string= id_string
self.wine_ids = id_string.split(/,/)
end
end
The first method above is the "getter" - it takes the list of associated wine ids and converts them to a string that the form can use. The next method is the "setter", and it accepts a comma-delimited string of ids, and breaks it up into the array that wine_ids= accepts.
You might also be interested in my article Dynamic Form Elements in Rails, which outlines how rails form inputs aren't limited to the attributes in the database record. Any pair of accessor methods can be used.
I have a parent object, Post, which has the following children.
has_one :link
has_one :picture
has_one :code
These children are mutually exclusive.
Is there a way to use polymorphic associations in reverse so that I don't have to have link_id, picture_id, and code_id fields in my Post table?
I wrote up a small Gist showing how to do this:
https://gist.github.com/1242485
I believe you are looking for the :as option for has_one. It allows you to specify the name of the belongs_to association end.
When all else fails, read the docs: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one
Is there a way to use polymorphic
associations in reverse so that I
don't have to have link_id,
picture_id, and code_id fields in my
Post table?
has_one implies that the foreign key is in the other table. If you've really defined your model this way, then you won't have link_id, picture_id, and code_id in your Post table. I think you meant to say belongs_to.
I want to do something like
#post.postable and get the child
object, which would be one of link,
picture, or code.
I believe you could do this by using STI and combining the links, pictures, and codes tables, then testing the type of the model when retrieving. That seems kludgey though, and could end up with lots of unused columns.
Is there a reason for not storing the unused id columns, other than saving space? If you're willing to keep them, then you could define a virtual attribute and a postable_type column : (untested code, may fail spectacularly)
def postable
self.send(self.postable_type)
end
def postable=(p)
self.send(postable_type.to_s+"=",p)
end