I have an instance where I am recording prices for water from vendors. My vendor model has :price. However, I want to give users the option to input a price for different volumes, and do the simple division for them rather than having them to do it. In other words, users should be able to input $1.99 per liter or $3.99 for a gallon and so on. To do this, I need a virtual attribute in my form for :unit, since I don't want to be storing units in the table. Everything works well, except that I cannot seem to update vendor_params[:price] before I update the record or create a new record. This seems like it should be a cake walk, but I Googled most of the day and can't figure out how to make it work.
Here is what I have:
Model:
class Vendor < ActiveRecord::Base
attr_accessor :unit
...
end
Form:
<%= form_for(#vendor) do |f| %>
...
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price %>
<%= select( "unit", "id", { "1 Liter" => "1", "Bottle (2 liters)" => "2", "Jerry Can (20 liters)" => "20"}) %>
</div>
...
<% end %>
Controller:
...
def update
vendor_params[:price] = vendor_params[:price].to_f/params[:unit][:id].to_f
respond_to do |format|
if #vendor.update(vendor_params)
format.html { redirect_to #vendor, notice: 'Vendor was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #vendor.errors, status: :unprocessable_entity }
end
end
...
end
I know that vendor_params[:price].to_f/params[:unit][:id].to_f returns the correct value. I just can't seem to assign that value to vendor_params[:price] before I update the record. I also tried the following which throws an error:
#vendor_params[:price] = vendor_params[:price].to_f/params[:unit][:id].to_f
It seems like this should be trivial! I guess I could use form_tag instead of form_for, but that seems odd when updating the full record. (The edit form has all fields for all the object attributes.) Anywho, I'm open to ideas and suggestions.
Thanks!!
If vendor_params is a strong_params method (which I'm assuming it is), it actually creates a new hash. So when you alter vendor_params... you're not actually changing your original params hash.
OK, why isn't vendor_params changing though... I dont care about params? WELL, vendor_params still points the original params hash assuming it looks something like:
def vendor_params
params.require(:vendor).permit(:price)
end
I think the link below is a similar issue and may present a useful solution. Hope I understood your problem correctly!
Modify ruby hash in place( rails strong params)
Related
(Rails 5.2) My record with JSON is saving correctly to the (Postgres 9.4) database on CREATE but is getting reformatted after UPDATE, with carriage returns (\r) and newline characters (\n) showing up in the database after UPDATE.
The :budget_json column is a jsonb type.
Here is my (simplified) Budget model:
class Budget < ApplicationRecord
after_initialize :create_shell_json
def create_shell_json
self.budget_json = blank_budget if self.new_record?
end
def blank_budget
{
id: nil,
name: nil,
year: nil,
[etc...]
}
end
end
Here is my controller:
def new
#budget = Budget.new(year: Time.now.year)
end
def create
#budget = Budget.new(budget_params)
#budget.budget_json = JSON.parse(budget_params[:budget_json])
if #budget.save
redirect_to admin_budgets_path, notice: "Budget successfully created."
else
render :new
end
end
def edit
#budget = Budget.find(params[:id])
end
def update
#budget = Budget.find(params[:id])
#budget.budget_json = JSON.parse(budget_params[:budget_json])
if #budget.update(budget_params)
redirect_to admin_budgets_path, notice: "Budget successfully updated."
else
render :edit
end
end
And here are the relevant parts of the form. (The form is the same for CREATE and UPDATE.) The TEXTAREA contains the editable JSON should the user want to amend the default values:
<%= form_with model: [:admin, #budget], local: true, :html => {:class => "form-horizontal"} do |f| %>
...
<div class="form-group">
<div class="col-sm-2">
<%= f.label :budget_json %>
</div>
<div class="col-sm-2">
<%= text_area_tag "budget[budget_json]", JSON.pretty_generate(#budget.budget_json), id: "budget_budget_json" %>
</div>
</div>
...
<% end %>
FWIW, the form looks like this:
As you can see here (from pgAdmin), the first record (id: 166) is clean and usable. It has only just been created. The second record (id: 167) is unusable as has been stored as a string instead:
What am I missing?
Ran into something similar, but managed to get around by modifying the params before passing them to the update method. So in your case something like:
def update
params = budget_params
params[:budget_json] = JSON.parse(params[:budget_json])
if #budget.update(params)
redirect_to admin_budgets_path, notice: "Budget successfully updated."
else
render :edit
end
end
Jeez. How often does writing the whole thing out help you think more clearly! I have the answer: in the UPDATE action I wasn't actually using the JSON.parsed version of the parameters. By changing
if #budget.update(budget_params)
to
if #budget.save(budget_params)
everything works as it should.
Having said that, if anyone is able to suggest a more elegant way of coding these (admin interface) round trips for JSON data, I'll be happy to hear your suggestions.
First steps with RoR, trying to wrap my head around basic concepts. Following excercise: I have pupils and schoolclasses, both Active Record entities with a many to many (has_and_belongs_to_many) to each other. Now I have a form to create a new pupil. On this form there is also a form.select to pick the class for the pupil, but I can´t get this to work, I can´t get the controller to create a new record for the join table.
Schoolclass.rb
class Schoolclass < ApplicationRecord
has_and_belongs_to_many :pupils
end
Pupil.rb
class Pupil < ApplicationRecord
has_and_belongs_to_many :schoolclasses
end
Relevant part of the _form.html.erb
<div class="field">
<%= form.label :schoolclass %>
<%= form.select(schoolclass.id, schoolclasses_for_select) %>
</div>
schoolclasses_for_select is just a helper for populating the select box
def schoolclasses_for_select
Schoolclass.all.collect{ |s| [s.name, s.schoolyear] }
end
Everything I have tried on the controller has failed miserably. Somehow, I mostly end up with the controller trying to pass the schoolclass (as a String) as an attribute to the new Pupil, or with a MethodNotFound error. In my understanding it should work something like this :
#klass = params[:schoolclass]
pupil.schoolclasses << #klass
but it doesn´t.
Thanks in advance for any help.
Edit1: the create code
def create
#pupil = Pupil.new(pupil_params)
respond_to do |format|
if #pupil.save
format.html { redirect_to #pupil, notice: 'Pupil was successfully created.' }
format.json { render :show, status: :created, location: #pupil }
else
format.html { render :new }
format.json { render json: #pupil.errors, status: :unprocessable_entity }
end
end
end
def pupil_params
params.require(:pupil).permit(:nachname, :vorname, :schoolclass)
end
That is the part that works. What I haven't managed is to find the correct Schoolclass record and pass it to the pupil.
Issues
First argument to your form.select should be the field name i.e. :schoolclass_id. You can still keep the label Schoolclass.
I believe you want id of schoolclass to be passed in params when selected. For that to happen, change your options for select to Schoolclass.all.collect{ |s| [s.name, s.id] }
Biggest, Your association says a pupil can have multiple schoolclasses but your form doesn't support it. Have you handled it some other way?
Fixes
So, do something like (this does not support multiple schoolclasses selection):
<%= form.select :schoolclass_id, Schoolclass.all.collect{ |s| [s.name, s.id] } %>
And in your controller
def create
#pupil = Pupil.new(pupil_params)
# Find schoolclass from `schoolclass_id` and associate it to `#pupil`
schoolclass = Schoolclass.find(params[:pupil][:schoolclass_id]) # Handle case when schoolclass not selected in form
#pupil.schoolclasses |= [schoolclass]
respond_to do |format|
...
end
end
private
def pupil_params
params.require(:pupil).permit(:nachname, :vorname)
end
When I am creating a skid, I am trying to get a value that user has entered so that I can create that record that many times.
in _form.html.erb is where all my code sits. and in the new.html.erb is where I call the form with:
<%= render 'form' %>
Here is the piece of code from form that I am trying to access:
<%= f.label :skid_count %>
<%= f.number_field :skid_count, :value => '1', :required => 'required', :pattern => ValidationValues.c_integer, :placeholder => ValidationValues.p_integer %>
In the controller I am trying to do this:
def create
#skid = Skid.new(params[:skid])
count = params[:skid_count].to_i
# Create record in the database, and return an appropriate message
respond_to do |format|
if #skid.save
for i in 1..count
Skid.new(params[:skid]).save
end
format.html { redirect_to #skid, notice: 'Skid was successfully created.' }
format.json { render json: #skid, status: :created, location: #skid }
else
format.html { render action: "new" }
format.json { render json: #skid.errors, status: :unprocessable_entity }
end
end
end
for some reason the count variable is not picking up the number, if I hard code it and put 3 in there, it would create the record 4 times just as intended, however if I try to get the numeric value based on what user entered, as shown above, it doesn't works. It creates just 1 record every time.
Is there a reason why I cannot access that param?
It's look like you are setting the variable before the record saves, so there is no record to set the variable with at that point. If you move the line down a few spaces, it should work.
def create
#skid = Skid.new(params[:skid])
# Create record in the database, and return an appropriate message
respond_to do |format|
if #skid.save
count = params[:skid_count].to_i # if the record saves, create variable with the new params
for i in 1..count # do your magic
Skid.new(params[:skid]).save
end
I have solved this question by getting the value from the attribute in this manner:
count = params[:skid]["skid_count"]
I hope this helps somebody else stumbled with the same problem.
I will expand on your answer to explain why.
As you are building the field via the form_for helper, it automatically scopes it under the model attributes in the param hash.
params[:skid][:skid_count] would work as well
if you want :skid_count to be outside of the params hash (as to not to trigger forbidden attributes in newer versions of rails, you can build it by just using number_field_tag(:skid_count) which would them be available to your controller as params[:skid_count]
I am trying to create an article.
class Article < ActiveRecord::Base
belongs_to :article_skill
attr_accessible :articles_skill_attributes
accepts_nested_attributes_for :articles_skill
end
class ArticlesSkill < ActiveRecord::Base
attr_accessible :description, :name
has_many :articles
end
This is my form in the article/new.html.erb
<%= article_form.fields_for :articles_skill, #article.articles_skill do |b|%>
<label class="medium"><span class="red">*</span> Skill</label>
<%= b.select :id, options_for_select(ArticlesSkill.all.collect{|m| [m.name, m.id]}) %>
<%end%>
Here the article_form is the builder for the #article form object.
If I try to save the #article object its showing this error.
Couldn't find ArticlesSkill with ID=1 for Article with ID=
I've been struggling with this problem for a few days. Did a lot of searching.. it took going to the rails console and searching by the exception being thrown instead to make any progress with this.
Check out this answer on this question for why it's happening, and possible workarounds.
Use rails nested model to *create* outer object and simultaneously *edit* existing nested object?
Be aware that using the first option presented here creates a security hole as described in http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-3933
The second parameter in your fields_for call seems unnecessary. ActiveRecord is performing a lookup on the association articles_skill for #article when it reaches that param, but since the #article is new and has yet to be saved, it has no ID and triggers an error.
<%= article_form.fields_for :articles_skill do |b|%>
<label class="medium"><span class="red">*</span> Skill</label>
<%= b.select :id, options_for_select(ArticlesSkill.all.collect{|m| [m.name, m.id]}) %>
<% end %>
I can suggest only a workaround. It works, but I don't like it - I want some out-of-the-box solution.
I assume you have a function:
def articles_skill_params
params.require(:articles_skill).permit(:description, :name,
article_attributes: []) end
Add a function
def articles_skill_params2
params.require(:articles_skill).permit(:description, :name)
end
Add another function:
def set_article
article_id = articles_skill_params[:article_attributes][:id]
article = Article.find(article_id)
#articles_skill.articles << article
#articles_skill.save
end
Change your ArticlesSkillController#create:
def create
#articles_skill = ArticlesSkill.new(articles_skill_params2)
set_article
respond_to do |format|
if #articles_skill.save
format.html { redirect_to #articles_skill, notice: 'Article skill was successfully created.' }
format.json { render :show, status: :created, location: #articles_skill }
else
format.html { render :new }
format.json { render json: #articles_skill.errors, status: :unprocessable_entity }
end
end
end
As you can see, we simply exclude the nested attributes from the parent object creation (thus eliminating the error), then manually add them later.
If you just want people to be able to select an existing skill you don't need nested attributes at all (that's useful for when you might want people to be able to create an article skill from the same form that creates an article). You just want to set article_skill_id to an existing value, so you can just do
<%= form_for(#article) do |f| %>
...
<label class="medium"><span class="red">*</span> Skill</label>
<%= f.select :article_skill_id, ArticlesSkill.all.collect{|m| [m.name, m.id]}) %>
<% end %>
All, I have two dropdown boxes, which are populated from two different database tables and a form with a single submit button. My goal is to concatenate the two values upon form submit and write the single value back to the database into the form associated with the model.
More simply: two dropboxes allowing to select ['red','green','blue'] and ['dog','cat']. The user selects 'red' and 'cat', and the submit button creates a new record 'red-cat' (under the blogname model) as a result.
ENTIRE Form (new.html.erb) code:
<%= select("subdomainw1", "blognamew1", Subdomainw1.order("blognamew1 ASC").collect {|p| [ p.blognamew1 ] }, {:prompt => 'Select Adjective'}) %>
<%= select("subdomainw2", "blognamew2", Subdomainw2.order("blognamew2 ASC").collect {|p| [ p.blognamew2 ] }, {:prompt => 'Select Noun'}) %>
<%= simple_form_for (#blogname) do |f| %>
<%= f.button :submit %>
<% end %>
with the associated controller def create being:
def create
#blogname = Blogname.new(params[:blogname])
respond_to do |format|
#blogname.blogname = ?? THIS SHOULD BE A CONCATENATION OF THE VALUES FROM ABOVE SELECTS
if #blogname.save
format.html { redirect_to #blogname, notice: 'Blog was successfully created.' }
else
format.html { render action: "new" }
end
end
end
Any ideas here?
There are a lot of ways to do this, the Rails way would be to do it in your model and keep your controllers skinny.
I think the most common way in rails you'll see this done is a callback. So, for this example you could set up a before_validation (or perhaps before_create if you don't want it to be changed if the blog is edited) call back in your mode, and assign your blogname from the two other attributes.
model.rb
before_validation :generate_blogname
def generate_blogname
self.blogname ||= "#{blognamew1}-#{blognamew2}".parameterize
end
Then in your controller:
controller.rb
def create
#blogname = Blogname.new(params[:blogname])
respond_to do |format|
if #blogname.save
format.html { redirect_to #blogname, notice: 'Blog was successfully created.' }
else
format.html { render action: "new" }
end
end
end
The parameterize method will make this work for subdomains by taking out special characters. The model shouldn't probably be called blogname, it should probably be a table blog with an attribute of name. So #blog = Blog.new, then #blog.name = "Two Subdomain Values"