Capturing current_user.id saving deeply nested records - ruby-on-rails

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.

Related

Confused about using or not using nested attributes

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?

Check if a table value in one model matches a table value in a different model

This question is kind of hard to ask, but basically, I have a Class model and a User model, each Class table has a token, and so does each User one. After the user submits a sign up form, how would I set the value of the users class_id in the create action? I've tried <%= f.hidden_field :app_id, :value => App.find_by_token(params[:key]) %>, but this doesn't work. Sorry for the long and confusing question, will be glad to answer more. Thanks in advance for any answers
It sounds as though you have a "relationship" where a User belongs to a Class and a Class could have many users. If that is the case then you should use rails Associations to make it easy for yourself. This would involve adding a 'has_many :users' to your Class model and a 'belongs_to :class' call to your User model. You would then just use the rails helpers to 'build' the object and save it with the association in the corresponding controllers.
The manual way to do it would be as follows from your controller:
def create
#This would involve you sending the proper class id as a hidden form field with the form field attribute named 'class_id'. You may need to add 'attr_accessor :class_id' to your User model.
if user.create(user_params)
blahblahblah
else
sorry blah blah
end
end
private
def user_params
params.require(:user).permit(:name, :email, :class_id, :etc)
end

Rails forms - Should I build `accepts_nested_attributes_for` associations in the Controller, Model, or View?

The Question
I have a parent that accepts_nested_attributes_for a child. So, when I have a form for the parent, I need to build the child so I can display form fields for it as well. What I want to know is: where should I build the child? In the Model, View, or Controller?
Why I Am Asking This
You may be shaking your head and thinking I'm a madman for asking a question like this, but here's the line of thinking that got me here.
I have a Customer model that accepts_nested_attributes_for a billing_address, like so:
class Customer
belongs_to :billing_address, class_name: 'Address'
accepts_nested_attributes_for :billing_address
end
When I present a form for a new Customer to the user, I want to make sure there is a blank billing_address, so that the user actually sees fields for the billing_address. So I have something like this in my controller:
def new
#customer = Customer.new
#customer.build_billing_address
end
However, if the user doesn't fill out any of the billing_address fields, but tries to submit an invalid form, they will be presented with a form that no longer has fields for the billing_address, unless I put something like this in the create action of my controller:
def create
#customer = Customer.new(params[:customer])
#customer.build_billing_address if #customer.billing_address.nil?
end
There is another issue, which is that if a user tries to edit a Customer, but that Customer doesn't have an associated billing_address already, they won't see fields for the billing_address. So I have to add somethign like this to the controller:
def edit
#customer = Customer.find(params[:id])
#customer.build_billing_address if #customer.billing_address.nil?
end
And something similar needs to happen in the controller's update method.
Anyway, this is highly repetitive, so I thought about doing something in the model. My initial thinking was to add a callback to the model's after_initialize event, like so:
class CustomerModel
after_initialize :build_billing_address, if: 'billing_address.nil?'
end
But my spidey sense started tingling. Who's to say I won't instantiate a Customer in some other part of my code in the future and have this wreak havoc in some unexpected ways.
So my current thinking is that the best place to do this is in the form view itself, since what I'm trying to accomplish is to have a blank billing_address for the form and the form itself is the only place in the code where I know for sure that I'm about to show a form for the billing_address.
But, you know, I'm just some guy on the Internet. Where should I build_billing_address?
Though this advice by Xavier Shay is from 2011, he suggests putting it in the view, "since this is a view problem (do we display fields or not?)":
app/helpers/form_helper.rb:
module FormHelper
def setup_user(user)
user.address ||= Address.new
user
end
end
app/views/users/_form.html.erb:
<%= form_for setup_user(#user) do |f| %>
Note that I had to change the helper method to the following:
def setup_user(user)
user.addresses.build if user.addresses.empty?
user
end
The controller remains completely unchanged.
If you know your model should always have a billing address, you can override the getter for this attribute in your model class as described in the docs:
def billing_address
super || build_billing_address
end
Optionally pass in any attributes to build_billing_address as required by your particular needs.
You would use build if you want to build up something and save it later. I would say, use it in nested routes.
def create
#address = #customer.billing_addresses.build(params[:billing_address])
if #address.save
redirect_to #customer.billing_addresses
else
render "create"
end
end
Something like that. I also use the build when I'm in the console.
You have to remember the principles of MVC, which is to create DRY(don't repeat yourself) code, which is efficiently distributed between the various moving parts of the app
accepts_nested_attributes_for Is Great For Keeping Things DRY
accepts_nested_attributes_for is a model function which allows you to pass data through an association to another model. The reason why it exists is to give you the ability to populate another model's data based on a single form, and is excellent for extending functionality without too much extra code
The problem you're citing is that if you want to use the code in other areas of the app, you'll end up having all sorts of problems
My rebuttal to that is in order to create as efficient an application as possible, you want to write as little code as possible - letting Rails handle everything. The accepts_nested_attributes_for function does allow you to do this, but obviously has a cost, in that you have to accommodate it every time you want to use it
My recommendation is to use what you feel is the most efficient code you can, but also keep to conventions; as this will ensure speed & efficiency
You should handle all these scenarios in controller, since it is not a responsibility of model.
Just in terms of keeping things DRY, you can write a method,
def build_customer(customer)
customer.build_billing_address if customer.billing_address.nil?
#add more code if needed
end
And inside controller you can call this method wherever it is needed. e.g.
def create
#customer = Customer.new(params[:customer])
if #customer.save
redirect_to #customer.billing_addresses
else
build_customer(#customer)
render "new"
end
end

Use rails nested model to *create* outer object and simultaneously *edit* existing nested object?

Using Rails 2.3.8
Goal is to create a Blogger while simultaneously updating the nested User model (in case info has changed, etc.), OR create a brand new user if it doesn't exist yet.
Model:
class Blogger < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
Blogger controller:
def new
#blogger = Blogger.new
if user = self.get_user_from_session
#blogger.user = user
else
#blogger.build_user
end
# get_user_from_session returns existing user
# saved in session (if there is one)
end
def create
#blogger = Blogger.new(params[:blogger])
# ...
end
Form:
<% form_for(#blogger) do |blogger_form| %>
<% blogger_form.fields_for :user do |user_form| %>
<%= user_form.label :first_name %>
<%= user_form.text_field :first_name %>
# ... other fields for user
<% end %>
# ... other fields for blogger
<% end %>
Works fine when I'm creating a new user via the nested model, but fails if the nested user already exists and has and ID (in which case I'd like it to simply update that user).
Error:
Couldn't find User with ID=7 for Blogger with ID=
This SO question deals with a similar issue, and only answer suggests that Rails simply won't work that way. The answer suggests simply passing the ID of the existing item rather than showing the form for it -- which works fine, except I'd like to allow edits to the User attributes if there are any.
Deeply nested Rails forms using belong_to not working?
Suggestions? This doesn't seem like a particularly uncommon situation, and seems there must be a solution.
I'm using Rails 3.2.8 and running into the exact same problem.
It appears that what you are trying to do (assign/update an existing saved record to a belongs_to association (user) of a new unsaved parent model (Blogger) is simply not possible in Rails 3.2.8 (or Rails 2.3.8, for that matter, though I hope you've upgraded to 3.x by now)... not without some workarounds.
I found 2 workarounds that appear to work (in Rails 3.2.8). To understand why they work, you should first understand the code where it was raising the error.
Understanding why ActiveRecord is raising the error...
In my version of activerecord (3.2.8), the code that handles assigning nested attributes for a belongs_to association can be found in lib/active_record/nested_attributes.rb:332 and looks like this:
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present? && !assignment_opts[:without_protection]
raise_nested_attributes_record_not_found(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
end
end
end
In the if statement, if it sees that you passed a user ID (!attributes['id'].blank?), it tries to get the existing user record from the blogger's user association (record = send(association_name) where association_name is :user).
But since this is a newly built Blogger object, blogger.user is going to initially be nil, so it won't get to the assign_to_or_mark_for_destruction call in that branch that handles updating the existing record. This is what we need to work around (see the next section).
So it moves on to the 1st else if branch, which again checks if a user ID is present (attributes['id'].present?). It is present, so it checks the next condition, which is !assignment_opts[:without_protection].
Since you are initializing your new Blogger object with Blogger.new(params[:blogger]) (that is, without passing as: :role or without_protection: true), it uses the default assignment_opts of {}. !{}[:without_protection] is true, so it proceeds to raise_nested_attributes_record_not_found, which is the error that you saw.
Finally, if neither of the other 2 if branches were taken, it checks if it should reject the new record and (if not) proceeds to build a new record. This is the path it follows in the "create a brand new user if it doesn't exist yet" case you mentioned.
Workaround 1 (not recommended): without_protection: true
The first workaround I thought of -- but wouldn't recommend -- was be to assign the attributes to the Blogger object using without_protection: true (Rails 3.2.8).
Blogger.new(params[:blogger], without_protection: true)
This way it skips the 1st elsif and goes to the last elsif, which builds up a new user with all the attributes from the params, including :id. Actually, I don't know if that will cause it to update the existing user record like you were wanting (probably not—haven't really tested that option much), but at least it avoids the error... :)
Workaround 2 (recommended): set self.user in user_attributes=
But the workaround that I would recommend more than that is to actually initialize/set the user association from the :id param so that the first if branch is used and it updates the existing record in memory like you want...
accepts_nested_attributes_for :user
def user_attributes=(attributes)
if attributes['id'].present?
self.user = User.find(attributes['id'])
end
super
end
In order to be able to override the nested attributes accessor like that and call super, you'll need to either be using edge Rails or include the monkey patch that I posted at https://github.com/rails/rails/pull/2945. Alternatively, you can just call assign_nested_attributes_for_one_to_one_association(:user, attributes) directly from your user_attributes= setter instead of calling super.
If you want to make it always create a new user record and not update existing user...
In my case, I ended up deciding that I didn't want people to be able to update existing user records from this form, so I ended up using a slight variation of the workaround above:
accepts_nested_attributes_for :user
def user_attributes=(attributes)
if user.nil? && attributes['id'].present?
attributes.delete('id')
end
super
end
This approach also prevents the error from occurring, but does so a little differently.
If an id is passed in the params, instead of using it to initialize the user association, I just delete the passed-in id so that it will fall back to building a new user from the rest of the submitted user params.
I ran into the same error in rails 3.2. The error occurred when using a nested form to create a new object with a belongs to relationship for an existing object. Tyler Rick's approach did not work for me. What I found to work was to set the relationship following the initialization of the object and then setting the objects attributes. An example of this is as follows ...
#report = Report.new()
#report.user = current_user
#report.attributes = params[:report]
assuming params looks something like ...
{:report => { :name => "name", :user_attributes => {:id => 1, { :things_attributes => { "1" => {:name => "thing name" }}}}}}
Try adding a hidden field for the user's id in the nested form:
<%=user_form.hidden_field :id%>
The nested save will use this to determine if it is a create or an update for the User.

Formtastic and Formtaghelper

I'd like to create a user registration form where the user ticks some boxes that do not connect to the model.
For example, there might be a 'terms & conditions' box that I don't want to have a boolean field in the User model saying 'ticked Terms & Conditions'. Instead I want to create a record somewhere else (like a transaction) that recorded the date/time they accepted the T&Cs.
Another example might be some preference they indicated that I'll use later and hold in the session for now, like 'remember me'.
I can mix these types of fields with the regular form helper. How could I do either one of the examples above when using formtastic? It kind of sticks to have to mix traditional rails tags with lovely clean formtastic code.
You can create any number of virtual attributes in your model that do not necessarily need to be tied to a database column. Adding attr_accessor :terms_and_conditions to your user model will make this 'field' available to formtastic -- even though it's not a database field. You can validate it like any other field or create your own setter method to create a record elsewhere if that's what you need.
I'm inclined to disagree with the approach to use attr_accessors for action-specific entry elements. If Ts&Cs need to be recorded then that makes sense, but sometimes you need data that really is unrelated to the model and is only related to the specific action at hand, such as 'perform some heavyweight operation when executing the action'.
Lets say you have a sign-up form, and you're not using OAuth, and you have an option to specify twitter username and password on sign up. This is fine:
<%= form.input :twitter_username %>
<%= form.input :twitter_password, :as => :password %>
But this bit below confuses me -- its like formtastic in this case is actually taking away what is already there. Is there a way of adding params[:your-object] while still getting formastic to do all its lovely layout stuff?
How about:
class User < ActiveRecord::Base
...
#I don't want this here. Its only for UserController#create.
#attr_accessor :tweet_when_signed_up
...
end
and:
<%= form.input :tweet_when_signed_up, :as => :checkbox, :param_only => true %>
param_only is my made-up suggestion. It says 'this isn't even a transient property. Its just for this action.
class UserController < ActionController::Base
...
def create
if params[:tweet_when_signed_up] # haven't done this yet -- == 1 or !.nil?
Tweeter.tweet( ... )
end
#user = User.create( params[:user] )
end
The ability to do this is probably there -- does anyone know how to do effectively what I think is a good idea above?
thanks!
Instead I want to create a record
somewhere else (like a transaction)
that recorded the date/time they
accepted the T&Cs.
Use the attr_accessor that bensie describes to integrate the field with formtastic.
Formtastic is about view logic, while the relationship are more model logic. Don't worry about creating the related record in the form. Instead, use callbacks like before_update and after_save in the model to ensure the related record has been created.

Resources