Rails gurus, do you know of a standard solution to this problem I've been struggling with?
In my app, the user can define properties for his objects. So before generating his list of objects (let's say they are books), he can specify which properties he cares about and their potential values, and then for each book he will have to input a legal value for each property. So say I put in for my properties: length (legal values "long", "short") and difficulty ("easy", "hard"). On a different bookshelf, a different list of books could have different properties (cover_color "blue" or "red")
So now I am in my book form. "Add new book on this bookshelf." On the partial, I come up with the list of properties relevant to a new book on this bookshelf (length, and difficulty). Then I look up the legal values for each property. I have a select dropdown in which the user can choose one:
<% for prop in #book.properties %>
<%= prop %> :
<%= f.collection_select :prop_value_select, prop.legal_property_values, :id, :name %>
<%end %>
In my book model, I defined a virtual attribute that will create the "join record" PropertyValue. PropertyValue has legal_property_value_id and book_id.
def prop_value_select=(incoming_id_from_form)
PropertyValue.create!(:legal_property_value_id => incoming_id_from_form, :book=> self)
end
The whole scheme works, except my method is only getting called once, for the first property when the form submits, instead of once for each property.
I am racking my brain... seems very simple, but what's the standard rails way to do something like this?
collect all of the properties into an array and generate the models as a callback?
some magic with a partial for each property?
Thank you!
I think the problem is that you are using a nested model, and all your fields will have the same id's, so rails will only store one nested element.
You should be using fields_for which allows you to handle a nested model.
First you need to specify in your model that it will accept nested attributes. Add the following line
class Book
has_many :properties
accept_nested_attributes_for :properties
end
and in your view you would write something like
<% fields_for :properties do |prop| %>
<%= prop %> :
<%= f.collection_select ...
<% end %>
Hope this helps.
Related
I have a Quiz model that has_many questions. I need to show all the ids of questions associated with a particular Quiz in text_field. At present my code is like this:
<%= text_field(:quiz, :quiz_questions) %>
This displays the object quiz_questions but I must display the ids of all the questions of that quiz. I want something like this:
<%= text_field(:quiz, :quiz_questions, :id) %>
How can I do that?
The second argument quiz_questions is your model method. Therefore you need to add one to your model that only returns the ids
# app/models/quiz.rb
def question_ids
questions.ids
end
in your view
<%= text_field(:quiz, :question_ids) %>
Background: My goal is for a view to display a list of "condition" has_many objects, which are themselves STI subclasses of a StateDescription. I want a user to be able to pick what type of state description they are from a drop down menu (which will conditionally display a different type of form, eventually)
Inside of my main forms, I am doing a nested form like this:
<%= f.fields_for :conditions do |e| %>
<li>
<%= e.select(:type, StateDescription.subclasses.collect{|x| x.to_s}, options_for_select(StateDescription.subclassSelectForms)) %>
<br>
<%= e.label :title %>
<%= e.text_field :title %>
</li>
<% end %>
This works just fine with the text field at the bottom there (I can change values and save them, etc). But when I try to do a select statement, it explodes.
Specifically, if I don't use e.select and just do:
<%= select(:type, StateDescription.subclasses.collect{|x| x.to_s}, options_for_select(StateDescription.subclassSelectForms)) %>
it renders just fine, but doesn't actually change any values as it is not associated with a model.
If I get rid of trying to have it display a value different than it submits and just do
<%= e.select(:type, StateDescription.subclasses.collect{|x| x.to_s}) %>
Then it works just fine(I can submit, the value is saved to the database, I can retrieve it, etc).
I can LEAVE it like this, but I had wanted to have my more human readable string display instead of a rails class thing (which might look like gibberish to a non-programmer end user).
So, my question is: Why do options_for_select break when nested and associated with a form, but dont' seem to when not associated with a form? Is it just a known bug? Am I doing something wrong?
Edit:
.subclasses is a standard rails calls which returns a list of subclasses for an object.
.subclassSelect forms goes through each subclass, and turns it into a hash of the form:
{subclass.to_s => subclass.human_readable_string} and compiles all of them into an array.
I know this array of hashes works, because if I do NOT put "e" on the front, it displays correctly and works "right" for what I have done (i.e. the subclass is returned correctly based on which human readable string I select from the drop down menu, but since it's not actually associated with any form, it doesn't get set in the controller).
For database columns that are integers that "represent" strings, what is the best way to show the string value in the view?
For example, if I collect "payment_method" values as integers in my form as follows:
<%= f.select :payment_method, { "Visa" => "1", "Mastercard" => "2", "Amex" => "3"} %>
How can I show the saved integer as a string in my view files? What can I add to my model, so that
<%= #relevantvariable.payment_method %>
or something similar returns string values like "Visa", "Mastercard" or "Amex" instead of their respective integer values?
Thanks much for handling such a basic question!
Either don't use an integer value, and store the payment method directly as a string in the db, or create a PaymentMethod model.
With the association set up, you'd be able to refer to the name of the payment method as so:
<%= #relevantvariable.payment_method.name %>
Don't try to manually handle lists of names / ids - that will quickly get unmanageable.
Edit: after reading your comment, if you went with the first option and stored a string in the table, definitely don't allow the user to type the value directly, maintain a list on the model (or similar), that you seed your dropdown from, that way you're still constraining the possible values. You could even add a custom validator if you want to be certain what you putting in the database.
I'd stick with cheeseweasel solution but you can do one thing to show that on your view...
<% if #relevantvariable.payment_method == 1 %>
<%= "Visa" %>
<% else %>
<%= "Mastercard" %>
You probably would want to use a switch/case instead but you got the idea
As I said I think you should stick with cheeseweasel solution since there are many problems with this approach... it's your call
So you have your payment methods in a separate table payment_methods and the owner ( say user) contains a has_one relationship with it
class User < AR::Base
has_one :payment_method
end
Then show the payment method name just like
<%=
#user.payment_method.name #or whatever you have.
%>
However, while you are loading the #user data, you can perform a eager loading by :include. Like
User.find(user_id, :include => :payment_method)
This will load the PaymentMethod data earlier even for multiple users with single query.
There are rfq, quote and test_item in our rails 3.1.0 app. RFQ has many quotes and test items. Quote belongs to a rfq and has many test items. Test item has many rfqs and quotes.
When creating a quote, the test items in the rfq are passed into the quote new form directly. Here is the code in new in quotes controller:
#quote = #rfq.quotes.new()
#quote.test_items << #rfq.test_items
Here is the view code in quote new form for displaying the test items passed from the rfq:
<%= simple_form_for([#rfq, #quote]) do |f| %>
....
<% #quote.test_items.each do |t| %>
<p><%= f.association :test_items, :label => false %><%= link_to_remove_fields "remove", f %></p>
<% end %>
....
<% end %>
The view code above can display the test item passed from the rfq and maintain the association (quote has many test items) which is what the app needs. However it also displays a selection box for test item which is not needed here(can't change the test item in quote other than delete. Also t in the loop hasn't been useful). What we need is only to display the name of the test items, maintain the association (quote has many test items) and allow to delete test item (done by link_to_remove_fields).
Is there a clean way to accomplish this? Thanks so much.
Take a look at the nested attributes mechanism in Rails. It requires both a model declaration, accepts_nested_attributes_for and is supported by the fields_for form helpers that are also compatible with simple_form's simple_fields_for.
I think your error is that you loop over the test_items explicitly and call f.assocation which tells the form that you'll want to choose a different test_item. I think what you're trying to do is to let the user remove test_items with a check box. You'll need to use fields_for to implicitly loop over the list of test_items and use nested_attributes to permit deletion.
i have a model named 'chapter' (whose only attributes are 'name' and 'course__id') which belongs to "course" (and a course has_many chapters). on the course 'Show' view, I list all chapters for that course. Easy.
I want to add a form at the end of the list so that a user can easily create a new chapter.
so in my controller, I've added this:
#newchapter=#course.chapters.build
and the form on the view looks like this:
<% form_for([#course,#newchapter]) do |c| -%>
<%= c.label :name, "New Chapter" %>: <%= c.text_field :name %>
<%= c.submit 'Create' %>
<% end %>
(for the sake of clarity: it is outside of the #course.chapters.each block)
Now, the problem is that #course.chapters.size is the actual number of chapters + the empty one i created in the controller.
Is there a way to loop through all #course.chapters except the last (empty) one? or is there a better practice (i.e. not create #newchapter or not like this)?
thanks,
Pierre
You don't want to use #course.chapters.build here because this does add an empty chapter to the course. Instead you'll want to use Chapter.new and set the :course option like this.
#newchapter = Chapter.new(:course => #course)
It may not even be necessary to specify :course here depending on how you are using #newchapter.