I am using a virtual attribute called :all_dates on my form . The point of this field is to replace the :purchase_date attribute of my UserPrice model with the date of my :all_dates field. The reason for this is so user's don't have to change the :purchase_date of all of the user_price records they want to create on the form (they can create a maximum of 5), so what it suppose to do is update the columns of the user_prices with the date that is given from the :all_dates field.
Problem
Unfortunately on creating 1 to 5 records of user_prices, I get a NoMethodError because of the :all_dates field:
NoMethodError (undefined method `user_prices' for #<UserPrice:0x485d918>):
app/models/user_price.rb:54:in `save_all_dates_to_user_prices'
app/controllers/user_prices_controller.rb:27:in `each'
app/controllers/user_prices_controller.rb:27:in `create_multiple'
UPDATE
I got rid of the NoMethodError by putting this in my UserPrice model:
def user_prices
#user_prices = Array.new() { UserPrice.new }
end
But that isn't correct because the :all_dates field doesn't update my UserPrice :purchase_date fields. Does anyone have any ideas?
Question
How do I define the method user_prices?
I am guessing its suppose to be able to loop several new records of UserPrice but how is that done?
Code
This form acts like a nested form but instead of using two or more models its just using one single model which is my UserPrice to generate more records on the form, in my case being 5 new ones.
<%= form_tag create_multiple_user_prices_path, :method => :post do %>
<%= date_select("user_price", "all_dates" %>
<% #user_prices.each_with_index do |user_price, index| %>
<%= fields_for "user_prices[#{index}]", user_price do |up| %>
<%= render "add_store_price_fields", :f => up %>
<% end %>
<% end %>
<% end %>
class UserPrice < ActiveRecord::Base
attr_accessible :price, :product_name, :all_dates
attr_accessor :all_dates
after_save :save_all_dates_to_user_prices
protected
def save_all_dates_to_user_prices
self.user_prices.each {|up| up.purchase_date = self.all_dates if up.new_record?}
end
class UserPricesController < ApplicationController
def new
#user_prices = Array.new(5) { UserPrice.new }
end
def create_multiple
#user_prices = params[:user_prices].values.collect { |up| UserPrice.new(up) }
if #user_prices.all?(&:valid?)
#user_prices.each(&:save!)
redirect_to :back, :notice => "Successfully added prices."
else
redirect_to :back, :notice => "Error, please try again."
end
end
Re: Why receiving error undefined method `user_prices' for...
Ans: You need to define the method user_prices
Since you named the model (object) UserPrice, normally user_price would be used to represent an instance of the model.
You need to re-think what user_prices represents, an array of UserPrice objects/records? Or something else?
Added Do you want method save_all_dates_to_user_prices to iterate through all of the UserPrice records?
If so, then:
You probably want save_all_dates_to_user_prices to be a class method since it would be dealing with the multiple instances of the class.
The method would need to first load an array with all of the current records. Do this with the class method find or scope
I Took a totally different approach and was still able to get the same results in this Question: How to update a model's attribute with a virtual attribute?
Related
is a little project and I try to associate patient model with consultations. one patient has_many :consultations, in my form I have:
<%= f.association :patient %>
I pass the id parameter from the patient to the action 'new' in this way:
<%= link_to new_consultum_path(:id => #patient.id) %>
And in the view I have:
How can I make that the f.association field take the correspondent patient_id automatically?
How can I be sure that the patient_id is the current patient?
If I want to hide this field is that ok if I put
instead of
Is a better way to do this?
And why in the view shows me # patient:0x007f4e7c32cbd0 ?
thanks for your help.
And why in the view shows me # patient:0x007f4e7c32cbd0
This is a Patient object.
It means you need to call an attribute of this object - EG #patient.name.
--
f.association field take the correspondent patient_id automatically
This might help:
It looks like Organization model doesn't have any of these fields: [
:to_label, :name, :title, :to_s ] so SimpleForm can't detect a default
label and value methods for collection. I think you should pass it
manually.
#app/models/patient.rb
class Patient < ActiveRecord::Base
def to_label
"#{name}"
end
end
Apparently, you need to have either title, name or to_label methods in your model in order for f.association to populate the data.
-
How can I be sure that the patient_id is the current patient?
If you're having to verify this, it suggests inconsistencies with your code's structure. If you need the patient_id to be set as the current patient, surely you could set it in the controller:
#app/controllers/consultations_controller.rb
class ConultationsController < ApplicationController
def create
#consultation = Constultation.new
#consultation.patient = current_patient
#consultation.save
end
end
I can provide more context if required.
You want to associate consultations to patients using fields_for, which is similar to form_for, but does not build the form tags.
It you start with your patient object, you can iterate through the consultation associations binding it to form fields as you go.
it would look something like this
<%= form_for #patient do |patient_form| %>
<% patient_form.text_field :any_attribute_on_patient %>
<% #patient.consultations.each do |consultation| %>
<%= patient_form.fields_for consultation do |consultation_fields| %>
<% consultation_fields.text_field :any_attribute_on_consulatation %>
<% end %>
<% end %>
<% end %>
Sorry, the code may not be exactly right.
Check out the docs for field_for here
You will also have to set accepts_nested_attributes_for consultations on patient. When you set accepts_nested_forms_for, Rails will automatically update the associated consultations object to the patient and save any edits you have made. You DEFINITELY want to use accepts_nested_attributes_for most nested form handling of this type.
There are many students and each student belongs to a program.
I have a controller:
def edit
#student = Student.find(params[:id])
#programs = Program.all
end
Then I have an edit form (simplified):
<%= f.label(:program_id, "Program") %>
<%= f.select(:program_id, #programs.collect {|c| [c.name, c.id]}) %>
Finally, I have a model for the student:
class Student < ActiveRecord::Base
belongs_to :program
validates :first_name, presence: true
end
I can update a student, as long as I input a first name. If I leave first name blank, I get an error undefined method 'collect' for nil:NilClass
However, if I change the form just a little bit, everything works. Like so:
<%= f.label(:program_id, "Program") %>
<%= f.select(:program_id, Program.all.collect {|c| [c.name, c.id]}) %>
Notice the Programs.all in there. Instead of in the controller.
What gives? I'd really like to define #programs in the controller. Am I missing something obvious?
Thanks!
P.S. If I take the validation rule out of the model, everything works again--so I'm pretty sure the validation is at the heart of the matter.
Do you have an update method in your controller? My guess is in the update action you have an if statement that says if the save fails, render the edit template again. If you are rendering the edit template from the update method, the view no longer knows about #programs, so you would need to redefine again in the update method, or create a before_action.
I've googling and trying everything I could think of for the past couple of days to solve a relatively simple (I presume) issue with has_and_belongs_to_many relation.
I managed to successful use the HABTM relation to submit a single relation value. Here's the sample code:
Model:
class Livre < ActiveRecord::Base
has_and_belongs_to_many : auteurs
end
class Auteur < ActiveRecord::Base
has_and_belongs_to_many :livres
end
Controller:
def new
#livre = Livre.new
#auteurs = Auteur.all
end
def create
#livre = Livre.new(livre_params)
if #livre.save
redirect_to [:admin, #livre]
else
render 'new'
end
end
private
def livre_params
params.require(:livre).permit(:name, :auteur_ids)
end
View:
<% f.label :auteur %><br>
<% f.collection_select(:auteur_ids, #auteurs, :id, :name) %>
Posted Params:
{"utf8"=>"✓",
"authenticity_token"=>"mAXUm7MRDgJgCH00VPb9bpgC+y/iOfxBjXSazcthWYs=",
"livre"=>{"name"=>"sdfsdfd",
"auteur_ids"=>"3"},
"commit"=>"Create Livre"}
But when I try to add "multiple true" to the view's collection_select helper, the (now multiple) relation doesn't get saved anymore. Sample code:
(both Model and Controller unchanged)
View:
<% f.label :auteur %><br>
<% f.collection_select(:auteur_ids, #auteurs, :id, :name, {}, {:multiple => true}) %>
Posted Params:
{"utf8"=>"✓",
"authenticity_token"=>"mAXUm7MRDgJgCH00VPb9bpgC+y/iOfxBjXSazcthWYs=",
"livre"=>{"name"=>"sdfsdf",
"auteur_ids"=>["1",
"5",
"3"]},
"commit"=>"Create Livre"}
As you can see, the params for "auteur_ids" is now an array. That's the only difference.
What am I doing wrong?
Just to clarify: both piece of code are able to add a new record to the livres db table, but only the 1st code is able to add the appropriate record to the auteurs_livres db table. The second one simply does not insert anything into auteurs_livres.
(I run on ruby 1.9.3p194 and rails 4.0.1)
Thanks!
Answer
For the fine folks stuck with the same problem, here's the answer:
Edit your controller and change the permitted parameter from :auteur_ids to {:auteur_ids => []}
params.require(:livre).permit(:name, {:auteur_ids => []})
And it now works :)
For the fine folks stuck with the same problem, here's the answer:
Edit your controller and change the permitted parameter from :auteur_ids to {:auteur_ids => []}
params.require(:livre).permit(:name, {:auteur_ids => []})
And it now works :)
You worked out the solution because Rails now expects auteur_ids to be an array, rather than a single item. This means that instead of just passing a single entity to the model, it will package the params as [0][1][2] etc, which is how you can submit your HABTM records now
There is a more Rails way to do this, which is to use accepts_nested_attributes_for. This is going to seem like a lot more work, but it will dry up your system, and also ensure convention over configuration:
Model
class Livre < ActiveRecord::Base
has_and_belongs_to_many : auteurs
accepts_nested_attributes_for :auteurs
end
class Auteur < ActiveRecord::Base
has_and_belongs_to_many :livres
end
Controller
def new
#livre = Livre.new
#livre.auteurs.build
#auteurs = Auteur.all
end
def create
#livre = Livre.new(livre_params)
if #livre.save
redirect_to [:admin, #livre]
else
render 'new'
end
end
private
def livre_params
params.require(:livre).permit(:name, auteur_attributes: [:auteur_id])
end
Form
<%= form_for #livre do |f| %>
<%= f.text_field :your_current_vars %>
<%= f.fields_for :auteurs do |a| %>
<%= a.collection_select(:auteur_id, #auteurs, :id, :name, {}) %>
<% end %>
<% end %>
This will submit the auteur_id for you (and automatically associate the livre_id foreign key in the HABTM model. Currently, this will only submit the number of objects which have been built in the new action -- so in order to add more items, you'll have to build more
I'm building a martial arts related database, currently I have the following associations set up:
Student has_and_belongs_to_many :styles
Style has_many :ranks
Student has_many :ranks, through: :gradings (and vice versa)
I'm generating a form as follows, depending on the student's styles:
So the headings are generated by the Style model (Tai Chi, Karate...), then their rankings listed below (taken from the Rank model), and the "Dojo" and "Date" fields should belong to the Grading model once created.
The question: I know how to build a form that creates one association (or one association + its children), but how do I build a form that creates multiple associations at once?
Also, what would be a clean way to implement the following:
Only lines which are ticked become associations
Dojo and date must be filled in for ticked lines to save successfully
If a line is unticked it will destroy any previously created associations
This is what I've currently implemented to retrieve the correct records:
class GradingsController < ApplicationController
before_filter :authenticate_sensei!
def index
#student = Student.includes(:styles).find(params[:student_id])
#ranks = Rank.for_student_styles(#student)
split_ranks_by_style
end
private
def split_ranks_by_style
#karate = #ranks.select_style("Karate")
#tai_chi = #ranks.select_style("Tai Chi")
#weaponry = #ranks.select_style("Weaponry")
end
end
# Rank model
def self.for_student_styles(student)
includes(:style).where("styles.id in (?)", student.styles.map(&:id))
end
def self.select_style(style)
all.map { |r| r if r.style.name == style }.compact
end
Complicated forms like this are best handled in a service object initiated in the primary resource's create or update action. This allows you to easily find where the logic is happening afterwards. In this case it looks like you can kick off your service object in your GradingsController. I also prefer formatting a lot of the data in the markup, to make the handling easier in the service object. This can be done a'la rails, by passing a name like "grade[style]" and "grade[rank]". This will format your params coming in as a convenient hash: {grade: {style: "karate", rank: "3"}}. That hash can be passed to your service object to be parsed through.
Without really grasping the full extent of your specific requirements, let's put together an example form:
<%= form_for :grading, url: gradings_path do |f| %>
<h1><%= #rank.name %></h1>
<%- #grades.each do |grade| %>
<div>
<%= hidden_field_tag "grade[#{grade.id}][id]", grade.id %>
<%= check_box_tag "grade[#{grade.id}][active]" %>
...
<%= text_field_tag "grade[#{grade.id}][date]" %>
</div>
<%- end %>
<%= submit_tag %>
<%- end %>
With a form like this, you get your params coming into the controller looking something like this:
"grade"=>{
"1"=>{"id"=>"1", "active"=>"1", "date"=>"2013-06-21"},
"3"=>{"id"=>"3", "date"=>"2013-07-01"}
}
Nicely formatted for us to hand off to our service object. Keeping our controller nice and clean:
class GradingsController < ApplicationController
def index
# ...
end
def create
builder = GradeBuilder.new(current_user, params['grade'])
if builder.run
redirect_to gradings_path
else
flash[:error] = 'Something went wrong!' # maybe even builder.error_message
render :action => :index
end
end
end
So now we just need to put any custom logic into our builder, I'd probably recommend just making a simple ruby class in your /lib directory. It could look something like this:
class GradeBuilder
attr_reader :data, :user
def self.initialize(user, params={})
#user = user
#data = params.values.select{|param| param['active'].present? }
end
def run
grades = data.each{|entry| build_grade(entry)}
return false if grades.empty?
end
private
def build_grade(entry)
grade = Grade.find(entry['id'])
rank = grade.rankings.create(student_id: user, date: entry['date'])
end
end
There will obviously need a lot more work to pass all the specific data you need from the form, and extra logic in the GradeBuilder to handle edge cases, but this will give you a framework to handle this problem in a maintainable and extensible way.
I think I am on the right path for the following, though i cannot save the form data to the model. I have 2 models
class Prediction < ActiveRecord::Base
attr_accessible :home_team, :away_team, :home_score, :away_score, :fixtures_attributes
has_many :fixtures
accepts_nested_attributes_for :fixtures
end
class Fixture < ActiveRecord::Base
attr_accessible :home_team, :away_team, :fixture_date, :kickoff_time
belongs_to :predictions
end
To create a new prediction record i have a form that takes all the fixtures and pre populates the form and the user will just add scores next to each team
<%= form_for #prediction do |f| %>
<!-- Gets all fixtures -->
<%= f.fields_for :fixtures, #fixtures<!-- grabs as a collection --> do |ff| %>
<%= ff.text_field :home_team %> VS <%= ff.text_field :away_team %><%= f.text_field :home_score %><%= f.text_field :away_score %><br>
<% end %>
<%= f.submit 'Submit Predictions' %>
<% end %>
Then i have my controller to take care of the new/create action, which i think is where i may be falling over
class PredictionsController < ApplicationController
def new
#prediction = Prediction.new
#prediction.fixtures.build
#fixtures = Fixture.all
end
def create
#prediction = Prediction.new(params[:prediction])
#prediction.save
if #prediction.save
redirect_to root_path, :notice => 'Predictions Submitted Successfully'
else
render 'new'
end
end
end
and finally my routes
resources :predictions
resources :fixtures
So when i submit the form i get the error
ActiveRecord::RecordNotFound in PredictionsController#create
Couldn't find Fixture with ID=84 for Prediction with ID=
Looking at the params being parsed (snapshot below), something does not look right, for one the home_score and away_score are not being passed through.
{"utf8"=>"✓",
"authenticity_token"=>"DfeEWlTde7deg48/2gji7zSHJ19MOGcMTxEsQEKdVsQ=",
"prediction"=>{"fixtures_attributes"=>{"0"=>{"home_team"=>"Arsenal",
"away_team"=>"Norwich",
"id"=>"84"},
"1"=>{"home_team"=>"Aston Villa",
"away_team"=>"Fulham",
"id"=>"85"},
"2"=>{"home_team"=>"Everton",
"away_team"=>"QPR",
"id"=>"86"}
Current Output of form
Any advice appreciated
Thanks
When you set #fixtures to Fixture.all in the new method in your prediction controller, you are including every fixture, not just the fixtures belonging to your prediction. When the results of the form are passed to the create controller there are fixtures associated with other predictions being passed in which is the source of the error you have reported. Perhaps you want something like #fixtures = #prediction.fixtures.
What you are doing in the fields_for block also looks fairly wrong to my eyes. You are using f.text_field for your home_score and away_score inputs. This will repeat the same form element for the prediction model in each fixture field. You won't get the result you want from this. To be honest, I'm struggling to understand how this association makes sense. Are you able to explain it a little better? My suspicion is that your models are not quite set up the way you need them to be.
edit:
OK, I think I have a better idea of what you're trying to achieve now. You're trying to create many predictions in one form and prefill the home_team and away_team from a list of existing fixtures, right?
Okay, assuming that is so, you're definitely approaching it the wrong way. You don't need a many-to-one relationship between your prediction and fixture models (as you have correctly surmised). What you need to do is generate your form by iterating over the collection of fixtures and populating the home_team and away_team fields from the current fixture instance. Do you actually need these to be editable text fields, or are you just putting them in a text field so they get passed through? If so, you could use hidden fields instead.
The problem now though, is that Rails doesn't easily allow creating multiple records in the one form. It's not something I've done before and it would take me quite a bit of trial-and-error to make it work, but here's a best guess for one way of making it so.
models
class Prediction < ActiveRecord::Base
attr_accessible :home_team, :away_team, :home_score, :away_score, :fixtures_attributes
end
class Fixture < ActiveRecord::Base
attr_accessible :home_team, :away_team, :fixture_date, :kickoff_time
end
controller
class PredictionsController < ApplicationController
def new
#prediction = Prediction.new
#fixtures = Fixture.all
end
def create
begin
params[:predictions].each do |prediction|
Prediction.new(prediction).save!
end
redirect_to root_path, :notice => 'Predictions Submitted Successfully'
rescue
render 'new'
end
end
end
view
<%= form_tag controller: 'predictions', action: 'create', method: 'post' do %>
<% #fixtures.each do |fixture| %>
<%= fixture.home_team %> vs <%= fixture.away_team %>
<%= hidden_field_tag "predictions[][home_team]", fixture.home_team %>
<%= hidden_field_tag "predictions[][away_team]", fixture.away_team %>
<%= text_field_tag "predictions[][home_score]" %>
<%= text_field_tag "predictions[][away_score]" %><br />
<% end %>
<%= submit_tag "Submit predictions" %>
<% end %>
So essentially I'm creating a form that returns an array of parameters. I can't use the model form helpers in this situation.
One way around this would be to create a dummy model that has_many predictions and create a nested form using this.
Anyway, that's a lot of untested code that may never get looked at so I'm going to leave it there for now.
I haven't spent a lot of time looking because my wife is making me leave soon. But if I understand things right each of your rows in your view is from fixtures and predictions model association. The params hash you are getting is a mess because default behavior is going to be a view that is updating a single record ID. So you are bringing multiple records into a single view and trying to update multiple records at once.
Your create method in your model
#prediction = Prediction.new(params[:prediction])
But your params hash being passed is this:
{"utf8"=>"✓",
"authenticity_token"=>"DfeEWlTde7deg48/2gji7zSHJ19MOGcMTxEsQEKdVsQ=",
"prediction"=>{"fixtures_attributes"=>{"0"=>{"home_team"=>"Arsenal",
"away_team"=>"Norwich",
"id"=>"84"},
"1"=>{"home_team"=>"Aston Villa",
"away_team"=>"Fulham",
"id"=>"85"},
"2"=>{"home_team"=>"Everton",
"away_team"=>"QPR",
"id"=>"86"}
So you have to get more logic in your create method in your predictions controller to iterate through the hash you are receiving and save each row one at a time.