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.
Related
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?
Good Day All!
Edited for better understanding.
First model is Inventory and in this model I have Product_Type, Product_Name and User_ID.
Second model I have Users which consist of First_Name, Last_Name and Pin_Number.
On my Inventories page I have a form for checking out said Product_Type and Product_Name, also a place for a user to put their Pin_Number in. On submit, it will check the Pin_Number they have typed in and validate it in the Users model and if the Pin_Number is correct it will create an entry with said Product_Type, Product_Name and User_ID (which is pulled from Pin_Number that was submitted.)
I am just trying to figure out how to validate that Pin_Number they submitted.
Thats why I thought I had to do some kind of validation and an if statement based on that validation. Not sure how to go about that.
I hope this clears up any confusion.
I am just trying to figure out how to validate that Pin_Number they submitted.
What constitutes a valid pin_number? Just that it allows you to successfully look up a User? What if a user enters another user's pin_number? Is that considered 'valid'? Something to think about...
It would be helpful if you would add to your question what your params look like upon form submission. But, we can do some guess work.
So, let's assume that params looks something like:
{..., "inventory"=>{"product_type"=>"foo", "product_name"=>"Bar"}, "pin_number"=>5, ...}
In your controller, you'll probably do something like:
if #user = User.find_by(pin_number: params[:pin_number])
#inventory = Inventory.new(inventory_params)
#inventory.user = #user
if #inventory.valid?
#inventory.save
# perhaps do some other stuff...
else
# handle the case where the `#inventory` is not valid
end
else
# handle the case where the `#user` was not found
end
This assumes you have something like:
private
def inventory_params
params.require(:inventory).permit(:product_type, :product_name)
end
In your Inventory model, you probably want to do something like (I apologize, I'm not on Rails 5 yet, so some of the syntax may be incorrect):
class Inventory < ActiveRecord::Base
validates :user_id,
:product_type,
:product_name,
presence: true
belongs_to :user
end
You probably also want to consider adding an index on User.pin_number if you're going to be doing a lot of finding by it.
Not sure if I got the question right, but sounds like a custom validator to me.
You can learn more about custom validators in the official Rails documentation under "Custom Validators"
Also, consider moving the class for the custom validator you'll build to a concern, which is a great way to make it reusable. You can find more information on this StackOverflow question and this nice tutorial.
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
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.
I'm trying to figure out how to make this hack for attr_accessible to support a really common use case in my code, but really after looking at attr_acessible and ActiveRecord::Base source code for a while, still don't know where to start. I can probably figure it out after digging deeper, but first I'd like to ask if anyone else would find this hack useful, if there's some other way to do this right now,
Here's the use case:
For many models, the attributes that should be accessible through mass assignment are different when creating the object and when updating the object. One simple example is a User model that has two attributes: username, and password. When the object is just being created, I want both username and password to be accessible through mass assignment. After the object is created, only password should be accessible through mass assignment, because it shouldn't be possible to change the username.
Of course I could just set the username for the object manually in my controller's create method, but I find that some version of this case happens with every model. If it was possible to specify a different set of attr_accessible attributes for creates and updates, I could continue to use my standard restful new and create methods (for example as provided by inherited_resources). Besides I think this info belongs in the model.
So here's how I think it could work:
class User < ActiveRecord::Base
attr_accessible :password
attr_accessible_create :email
attr_accessible_update :bio
...
What would happen is that when the object is created, password and email would be accessible through mass assignment. When the object is updated, email and bio would be accessible through mass assignment.
This could also work through black listing via attr_protected. Same example with black listing:
class User < ActiveRecord::Base
attr_accessible :email, :password, :bio
attr_protected_create :bio
attr_protected_update :email
...
Or alternatively the syntax could be more like this:
attr_accessible :password, :create => [:email], :update => [:bio]
With these hacks, you can continue to use User.update_attributes(params[:user]) and User.new(params[:user]) in your controllers and know that the mass assignment stuff is taken care of. If your controllers are created by something like inherited_resources (which seriously rocks and keeps getting better) or resource_controller, you don't have to worry about creating custom controller actions for this simple, common use case.
The questions to you, my expert Rails users:
Is there a way to do this now that I'm overlooking, as a Ruby/Rails newbie?
Would you find this functionality useful, if it were supported?
Which syntax do you like better:
attr_accessible_update and attr_accessible_create
attr_accessible :update => [], :create => []
support both, dude!
Should I make this into a gem?
If you know where the parameters are sanitized during new/create/update/build with attr_accessible/attr_protected values, it would be great if you could point it out.
Thanks!
ActiveRecord has an attr_readonly method that should do what you need.
I'm pretty sure this kind of functionality doesn't currently exist.
If you go ahead and try to implement this as a gem, I'd probably be a bigger fan of the #2 syntax you proposed. As a general rule now, you can only use either attr_protected or attr_accessible in a model once, and you can't mix them. Adding more attr_* statements might get confusing.
Should you implement this as a gem? Why not? No harm in putting something else out there that at least you find helpful.
Sorry I can't help you on your last point. :)