I am using https://github.com/plataformatec/simple_form and am trying to send a extra parameter. I have a Task, List and ListTask models, in the new page of the of the list I want to be able to insert the number of tasks that will be added. When you submit it will send you to the new list_task page with the correct number of forms populated.
=simple_form_for #list do |s|
=s.input :title
=s.input :task_count
=s.button :submit
This produces a error undefined method task_count, which makes sense because it is not a method in list.
if you don't want the value persisting to the db, add a virtual attribute:
class List < ActiveRecord::Base
attr_accessor :task_count
end
this will allow you to use that attribute in the form, but that will only persist for the life of that object but it will make it into your POST params.
...otherwise, if you want it to persist to the db (which is sounds like you may). You'd add task_count as a column in your lists table (via a migration).
Related
I have 4 tables:users, observative_session, observations, celestial_bodies.
Each user has many observative_session and each observative_session has many observations. I already put in the model the associations
So in observative_session I have a foreign_key (user_id) to link it to the user and in observation I have one foreign key (user_id) for the user and a foreign key (observative_session_id) for the observative_session plus another foreign key for the celestial_body (celestial_body_id).
I created a form in which I ask the user to insert the name of a celestial body
<%= f.text_field :celestial_body_id, label: 'Celestial body' %>
but I can't save the string as an id so I need to find the id corresponding to the inserted body and save it instead.
I tryed to define a virtual attribute
def celestial_body_name
CelestialBody.where(' ')
end
def celestial_body_name= (name)
celestyal_body_id = CelestialBody.where(name: celestial_body_name)
end
and then I create the new observation
def create
#observation = #observative_session.observations.build(observation_params)
....
end
but I get the undefined method 'observations' for nil:NilClass
I don't understand if I pass the parameters correctly or not.
Thank you for any help.
You should go with some autocomplete solution as https://github.com/bigtunacan/rails-jquery-autocomplete. You need to think about passing additional hidden field as celestial_body_id to create valid association in controller. I think this part should help you -- https://github.com/bigtunacan/rails-jquery-autocomplete#sending-extra-search-fields.
Of course you can still pass name without autocomplete, but it's bad for UI (you never know whether such category exists and you can even make some typo in it) and it will require additional queries on logic on backend side
How can I set a position field in the attributes for a nested model so that my has_many relationship remembers it's sort order?
I want to set the position from the index so that it reflects the order the relations have been dragged into.
I have a form with Nested fields, using the cocoon gem with JQuery Sortable allowing each field-set to be drag-sortable.
I want to update the order of all the fields on saving the form.
Try to use "act_as_list" gem.
class TodoList < ActiveRecord::Base
has_many :todo_items, -> { order(position: :asc) }
end
class TodoItem < ActiveRecord::Base
belongs_to :todo_list
acts_as_list scope: :todo_list
end
todo_list = TodoList.find(...)
todo_list.todo_items.first.move_to_bottom
todo_list.todo_items.last.move_higher
Refer: https://github.com/swanandp/acts_as_list
If you have the standard CRUD/restful routes and controller actions set up, then all you are wanting to do is to call "update_attributes(:position => 3)" on an instance of the nested class (education in this case).
The usual route to update an education which is nested under "resume" would be
UPDATE /resumes/123/educations/456
so, you'll be making an ajax call to this url. The "UPDATE" method isn't really an update method, it's sort of spoofed by passing through a parameter "_method" = "UPDATE", so your ajax call will need to include this parameter too.
So, basically, on the "finished dragging" event, you're going to be making an ajax call to this url
"/resumes/<%= #resume.id %>/educations/<%= education.id %>"
and passing data like
{"_method": "UPDATE", "education[position]": <new position>}
This should update the position of the education object.
The other remaining issue is that, with acts_as_list, when we change the position of one item, we want the others to adjust their position automatically, too. I'm not sure if acts_as_list does this automatically. Try it.
Ok, non ajaxy version.
Let's assume that your form is a form_for #resume, which means that it will call the resumes#create or resumes#update action.
If all the education rows in your list have a hidden field like this
<%= hidden_field_tag "resume[education_ids][]", education.id %>
then when the form is submitted, they will go through into an array in params[:resume][:education_ids] in the order in which they appear in the page when the form was submitted, which is what you want.
The association gives you the setter method Resume#education_ids, allowing you to set the associations, in order, this way.
Ie, your update action will (if it's a normal update action) be saying something like
#resume = Resume.find_by_id(params[:id])
if #resume.update_attributes(params[:resume])
...
in this case, this will be saying
#resume.update_attributes(:education_ids => [5,6,2,1])
which is like saying "set my educations to be those with ids 5,6,2,1, in that order.
CAVEAT: in my version of rails (this might be fixed in subsequent version), if you use this _ids method, and it already has associations, but in a different order, it WILL NOT reorder them. Give it a go and see what happens.
This can't be the best way to do it, but I have got this working in my controller.
p = 1
experiences = []
params[:user][:resume_attributes][:experiences_attributes].each do |e|
e = e.last.merge(:position=>p)
experiences << e
p = p + 1
end
params[:user][:resume_attributes][:experiences_attributes] = experiences
which at least illustrates what i want to achieve.
I currently have a working form to create a resource (An event booking) which belongs_to two other models, a Consumer (the customer) and a Course. In the Booking creation form, I'm using two hidden fields which pass through consumer_id and course_id.
For this to work in form_for, I've created two virtual attributes in my Booking model
attr_accessor :course_id, :consumer_id
And in the create event of BookingsController, I've grabbed those ID's from mass assignment and then manually assigned the actual Course and Consumer objects from the ID
bookings_controller.rb
def create
#booking = Booking.new(booking_params)
#booking.course = Course.find(#booking.course_id)
#booking.consumer = Consumer.find(#booking.consumer_id)
if #booking.save_with_payment
# Payment was successful, redirect to users account page to view it and past bookings
else
render :new
end
end
private
def booking_params
params.require(:booking).permit(:course_id, :consumer_id, :card_token, :visible, :created_at)
end
Is this best practice? I tried to name the form hidden fields as consumer and course, hoping that Rails would see that the value is an ID and automatically do a .find for me, but that doesn't appear to be the case. I'll be surprised if Rails can't take care of this automatically, I'm just not sure how to accomplish it.
It's simpler than you imagine and you're already most of the way there.
When you create a booking, you need only to set the course_id and consumer_id fields, so make sure you've got hidden fields set up in your form with these names and the right values:
<%= f.hidden_field :course_id, value: my_course_id %>
<%= f.hidden_field :consumer_id, value: my_consumer_id %>
Don't set course or consumer in your controller or in your form. That is, remove the following lines from your controller:
#booking.course = Course.find(#booking.course_id)
#booking.consumer = Consumer.find(#booking.consumer_id)
You already have course_id and consumer_id in your permit list, so when you post the form, the values for those parameters will be set on your new booking, which is all that you should care about.
When you attempt to access #booking.course, ActiveRecord will do a find for you based on the id set in course_id; this is handled by the belongs_to association that you've established in your model.
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'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.