I'm serializing a hash that is stored in a settings field in a table, and would like to be able to edit that hash in a form field.
class Template < ActiveRecord::Base
serialize :settings
end
But I just do <%= f.text_area :settings %> then the text area just shows the serialized data instead of the hash.
How can I get the hash to show in the text area?
Maybe setting up another accessor for your model would work.
class Template < ActiveRecord::Base
serialize :settings
attr_accessor :settings_edit
before_save :handle_settings_edit, :if => lambda {|template| template.settings_edit.present? }
def settings_edit
read_attribute(:settings).inspect # should display your hash like you want
end
protected
def handle_settings_edit
# You may want to perform eval in your validations instead of in a
# before_save callback, so that you can show errors on your form.
begin
self.settings = eval(settings_edit)
rescue SyntaxError => e
self.settings = settings_edit
end
end
end
Then in your form use <%= f.text_area :settings_edit %>.
I have not tested any of this code, but in theory it should work. Good luck!
WARNING: Using eval like this is very dangerous, in this example a user could delete the entire Template table with one line in the edit box Template.destroy_all. Use a different method to convert the string to a hash if user input is involved.
... or you could use something like this (without any logic in model):
<% #template.settings.each do |name, value| %>
<div>
<%= label_tag name %>
<%= text_field_tag "template[settings][#{name}]", value %>
</div>
<% end %>
you should use something like
class Template < ActiveRecord::Base
serialize :settings, Hash
end
Related
I'm trying to build a simple filter on my raffles index page to filter my raffles by category. However, none of the select options are populating- the drop down is completely blank. I think it's something to do with my view, I'm admittedly no expert with forms.
#app/controllers/raffles_controller.rb
def index
#raffles = Raffle.filter(params[:filter])
end
#app/models/raffle.rb
class Raffle < ApplicationRecord
has_many :tickets
has_many :users, through: :tickets
def self.filter(filter)
if filter
raffle = Raffle.where(category: filter)
else
Raffle.all
end
end
end
#app/views/raffles/index.rb
<h1>Current Raffles:</h1>
<%= form_tag(raffles_path, method: 'get') do %>
<%= select_tag(Raffle.pluck(:category).uniq, params[:filter]) %>
<%= submit_tag ("Filter") %>
<% end %>
any ideas?
Your call to select_tag is missing the name on the first parameter, and your list of options should be built as well.
To build the property list you can use the helper options_for_select
If you want to define a selected value, you can send it to options_for_select as well.
Based on your example, this should do the job:
<%= select_tag(:filter, options_for_select(Raffle.pluck(:category).uniq, params[:filter])) %>
A good source when you have questions about how a certain form helper works are https://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags
I have a JSONB field on my model users are able to CRUD via my API. This all works fine.
I'd like to be able to give internal staff/admin users the ability to edit the JSON settings object via a textarea form field. (The users are tech-savvy and will know how to write valid JSON.)
In the Rails UI, when I open the record, the form loads the JSON settings into the textarea fine, but on save the entire object is effectively wiped.
I can see the settings param is sent:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"bla_bla_bla", "users_notification"=>{"user_id"=>"48", "report_id"=>"2", "status"=>"1", "frequency"=>"1", "settings"=>"{\"threshold\"=>\"50\"}"}, "commit"=>"Update Users notification", "id"=>"18"}
and I am 99% sure I have whitelisted the params correctly:
def users_notification_params
params.require(:users_notification).permit(
:user_id,
:report_id,
:status,
:frequency,
:settings,
settings: [:threshold, :products, :avg_sales]
)
end
However, when I output the users_notification_params with puts, I can see that settings is nil:
{"user_id"=>"48", "report_id"=>"2", "status"=>"1", "frequency"=>"1", "settings"=>nil}
I figured I need to parse the settings param so set about putting a before_action callback in place, but when I output the users_notification_params I still see "settings"=>nil, so I have nothing to parse.
Any ideas?
I think that the issue is that Rails does not automatically parse the JSON string into a hash and you're trying to whitelist a hash. Try removing settings: [:threshold, :products, :avg_sales]. You then have to parse the JSON in the model or in the controller.
class Thing < ApplicationRecord
def settings=(val)
if val.is_a?(String)
begin
super JSON.parse(val)
rescue JSON::ParserError
errors[:settings].add('is not valid JSON')
super
end
end
end
end
In general it seems like a pretty clunky way to do something that can be done by using separate inputs for each setting. You can use store_accessor for this.
Instead of giving the admin the posibility to change a textfield you could give the form extra fields for your settings:
<%= form_with(model: users_notification, local: true) do |form| %>
<%= form.fields_for(:settings) do |sf| %>
<div class="field">
<%= sf.label :threshold %>
<%= sf.number_field :threshold %>
</div>
<% end %>
...
<% end %>
With this the params will be like
{"users_notification"=>{"settings"=>{"threshold"=>"1"}}
This will work with your permit.
Think I've solved this in the process of writing it. I'll post my solution below.
tl;dr Rails fields_for not generating the expected params.
I have a User class, with the following code:
class User < ActiveRecord::Base
has_one :woojit, dependent: :destroy, validate: true
accepts_nested_attributes_for :woojit
# ... more
end
and a Woojit class
class Woojit < ActiveRecord::Base
belongs_to :user
end
Our UsersController includes this method (we're using the gem Administrate, but it's a subclass of Rails' ApplicationController):
class WoojitsController < Admin::ApplicationController
def new
# ... other stuff
#woojit = #user.build_woojit
end
end
And our user _form.html.erb partial looks like this:
<%= form_for([namespace, page.resource], html: { class: "form" }) do |f| %>
<!-- User fields -->
<fieldset>
<legend>Woojit fields</legend>
<%= f.fields_for #woojit do |ff| %>
<%= render 'woojit_fields', f: ff %>
<% end %>
</fieldset>
<% end %>
So when I submit the form, what I'm hoping to see is a params hash with this subset:
{
"user" => {
"woojit_attributes" => {
"attr_1"=>"Foo", # etc
}
}
}
But what I'm actually getting in the create action is a params hash with this subset:
{
"user" => {
"woojit" => {
"attr_1"=>"Foo", # etc
}
}
}
Ie 'woojit' instead of 'woojit_attributes'. I could hack this in the create action, but that seems like a horrible way of resolving the problem. I want to know why the key is getting mis-named to begin with.
I originally tried the alternative of using the line <%= f.fields_for :woojit do |ff| %> (ie symbol instead of instance var), and not building a Woojit object in the #new action.
The #fields_for guide suggests in the One-To-One section that this should work, but the code in the block was never executed, so the line returned nil - and the woojit params never made it to the create action in any form.
So the problem was apparently that #fields_for behaves differently when given an instance of a model (#woojit), rather than a symbol (:woojit).
In the former instance it generates fields that reference (model name), eg:
<input type="text" name="user[woojit][woojit_attr]" id="user_pledge_pledgor_name" />
In the latter instance it generates fields fields that reference the model attributes, eg:
<input type="text" name="user[woojit_attributes][woojit_attr]" id="user_pledge_attributes_pledgor_name" />
Hope that saves someone some of the frustration I've just been through!
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.