I have a table without primary key with three columns - Player_ID, Season and Amount. When I want to update my current table I have this error message: ActiveRecord::StatementInvalid (Mysql2::Error: Unknown column 'salaries.' in 'where clause': UPDATE salaries SET Season = 20192, Amount = 3232.0 WHERE salaries.`` IS NULL)
Update method in my controller:
def new
#salary = Salary.new
#players = Player.all
#title = "Add salary"
end
def create
#players = Player.all
#salary = Salary.new(params[:salary])
if #salary.save
flash[:success] = "Successfully added salary"
redirect_to salaries_path
else
#title = "Add salary"
render 'new'
end
end
def edit
#player_id = params[:player_id]
#season = params[:season]
#salary = Salary.find_by_Player_ID_and_Season(params[:player_id], params[:season])
#players = Player.all
#title = "Edit salary"
end
def update
#players = Player.all
#player_id = params[:player_id]
#season = params[:season]
#salary = Salary.find_by_Player_ID_and_Season(params[:player_id], params[:season])
if #salary.update_attributes(params[:salary])
flash[:success] = "Successfully edited salary"
redirect_to salaries_path
else
render 'edit'
end
end
_form.html.erb
<%= form_for(#salary, :url => salary_path(:player_id => #player_id, :season => #season)) do |f| %>
<%= f.collection_select :Player_ID, #players, :ID, :Name %>
<%= f.text_field :Season %>
<%= f.text_field :Amount %>
<%= f.submit "Add" %>
<% end %>
Active Record models have to have a primary key or rows can't be updated or delete.
There is a composite_primary_keys gem if you really don't want to add an ID column to your table (although adding that primary key is certainly the path of least resistance)
Your table has already primary key! It's the id attribute.
The params[:season] ... i think it's params[:salary][:season], right?
I think you should write in downcase season, player_id, amount. After that, if it still has problem, post new code here^^
I know the reason for your error, although the root cause isn't immediately clear to me. Your failing query is this:
ActiveRecord::StatementInvalid (Mysql2::Error: Unknown column 'salaries.' in 'where clause': UPDATE salaries SET Season = 20192, Amount = 3232.0 WHERE salaries.`` IS NULL)
The important part is this:
WHERE salaries.`` IS NULL
It would make a certain amount of sense if it were WHERE salaries.foo IS NULL or WHERE salaries.bar IS NULL, but you just have empty space where a column name would normally be. So I guess MySQL (for some weird reason) is interpreting that to mean that you're talking about a column called salaries (i.e. salaries.salaries), which doesn't exist.
I don't even know why the WHERE clause is in there in the first place.
Does your create action work?
Update: the problem is almost certainly this line:
if #salary.update_attributes(params[:salary])
I believe what you want instead is:
if #salary.update_attributes(:amount => params[:amount])
See if that does anything.
Related
I have a form that allows user to register for a tournament. In the process of building the registration form I hae dynamic nested fields with so far field_type of basic. When I load the participants new form I am trying to load all the fields from the Fields table with field_type of basic. It will find them and if I just try <%= #basic.name %> from the new form it will give the name of the last field in the database with that field_type, but if I try:
<% #basic.each do |b| %>
<%= b.name %>
<% end
I get the error undefined method `each' for #<Field.
Here is the new action from the participants_controller:
def new
#participant = #event.participants.new
#user = User.find(current_user.id)
#children = #user.children
#basic = Field.find_by(event_id: #event.id, field_type: 'basic')
end
Fields belong to events but do I have to connect them to participants to make this work?
Thanks
find_by only returns a single record (or nil if the criteria aren't matched) use where to return a collection.
#basic = Field.where(event_id: #event.id, field_type: 'basic')
However assuming you have the association has_many :fields defined in Event you could also use:
#basic = #event.fields.where(field_type: 'basic')
And if you have the scope :basic, -> { where(field_type: 'basic') } defined in Field you can further simplify to:
#basic = #event.fields.basic
I have created a multi find search, where I need to filter records by date / category / title. Searching by a category and/or title works, however, when date is typed it doesn't change anything (the results is the same like there was no date typed). I have no idea what else I could do to fix it, I am just a beginner in Ruby. Any idea?
Model:
def self.multi_find(cat_id, search, date_search)
search_condition = "%" + search + "%"
#date_condition = date_search
# test if cat_id is not blank
if not cat_id.blank?
# assign the value of cat_id to a ‘scope’ named :cat
scope :cat, -> { where('category_id = ?', cat_id) }
# using the ‘scope’ cat find where a search string is like a title or an author’s name
self.cat.where("title LIKE ? or event_date = ?", search_condition, date_search.to_date)
else
# find where a search string is like a title or an author’s name
self.where("title LIKE ? or event_date = ?", search_condition, date_search.to_date)
end
end
Controller:
def multi_find
# call an Event class method, using two parameters; a category unique identifier and a search string (author or title)
events = Event.multi_find(params[:cat_id], params[:search_string], params[:event_date_search])
# use Kaminari pagination ...
#events = Kaminari.paginate_array(events.order :title).page(params[:page]).per(6)
# if no products have been found
if #events.empty?
# display a notice
flash.now[:alert] = "No events found - so displaying all events"
# then display all products
#events = Event.order(:title).page(params[:page]).per(6)
end
# use the index view
render :action => "index"
end
The console outputs the SQL Query
Event Load (0.0ms) SELECT "events".* FROM "events" WHERE (category_id = '1') AND (title LIKE '%%' or event_date = '2018-02-14') ORDER BY "events"."title" ASC
View file:
<%= form_tag my_path, :method=>'post', :multipart => true do %>
<%= select_tag ('cat_id'),
options_from_collection_for_select(#categories, :id, :cat_name, 0 ),
:prompt => "Select a Category" %>
<div class="datepicker">
<% #event_date_format %>
<%= text_field_tag :event_date_search %>
</div>
<!-- Key word:-->
<%= text_field_tag :search_string %>
<%= submit_tag 'Search' %>
<% end %>
It's because you have an or in your sql statement. However you should also clean up your code a bit.
def self.multi_find(cat_id, search, date_search)
result = self.all
result = result.where(category_id: cat_id) if cat.id.present?
result = result.where('title LIKE ?', "%#{search}%") if search.present?
result = result.where(event_date: date_search) if date_search.present?
result
end
I struggled to digest this into a title.
I'm using SimpleForm to construct a bulk-edit page with one or more fieldsets - one for each record in a collection of ActiveRecord models that have been built but not yet saved.
My form looks like this:
= simple_form_for :courses, method: :patch do |f|
- #courses.each do |course|
= field_set_tag do
= f.simple_fields_for 'courses[]', course do |c|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
The params hash for one record looks like this:
"courses"=>{"courses"=>[{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2017-07-31"}]}
with an array, while for two records it looks like this:
"courses"=>{"courses"=>{"1"=>{"title"=>"Course X", "start_date"=>"2018-01-16", "end_date"=>"2018-07-30"}, "2"=>{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2018-07-30"}}}
with a stringy-integer-keyed hash.
This becomes a problem when I try and use strong parameters. After much hacking, I ended up with this piece of code, which works for multiple records but fails when only one is submitted:
ActionController::Parameters
.new(courses: params[:courses][:courses].values)
.permit(courses: [:title, :start_date, :end_date])
.require(:courses)
It fails with param is missing or the value is empty: courses highlighting the .require(:courses) line above.
The problem is "solved" by harmonising the single-record case with the multiple-record case:
if params[:courses][:courses].is_a?(Array)
params[:courses][:courses] = { '1': params[:courses][:courses][0] }
end
but it feels like there should be a simpler way of doing it.
Is there a better way to write the form for this use-case? Am I missing a trick with strong parameters?
I'm using rails 5.0.5 and simple_form 3.5.0.
"but it feels like there should be a simpler way of doing it."
Yes, use ajax to send individual create/update requests. This can be done transparently to the user and provides simpler code and a far better user experience.
Rails has fields_for and accepts_nested_attributes that can be used to create/update multiple child records and the parent record in a single request. But it really requires a association that groups the records together and even at this can get really hacky and convoluted when it comes to validations.
You want to set it up so that you have a seperate form for each record:
- courses.each do |c|
= render partial: 'courses/_form', course: c
There is really nothing to the form:
# courses/_form.haml.erb
= simple_form_for course, remote: true, html: { 'data-type' => 'json', class: 'course_form'} do |f|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
Instead of using a js.erb template we use 'data-type' => 'json' and write our own handler as its easier to target the correct form:
$(document).on('ajax:success', '.course_form', function(event, xhr, status){
var $form = $(this);
alert('Course created');
if (this.method.post) {
// changes form to update instead.
this.method = 'patch';
this.action = xhr.getResponseHeader('Location');
}
});
$(document).on('ajax:error', '.course_form', function(event, xhr, status){
var $form = $(this);
// #todo display errors
});
Creating the controller is very straight forward:
class CoursesController
def create
#course = Course.new(course_params)
respond_to do |format|
if #course.save(course_params)
format.json { head :created, location: #course }
else
format.json do
render json: {
errors: #course.errors.full_messages
}
end
end
end
end
def update
#course = Course.find(params[:id])
respond_to do |format|
if #course.update(course_params)
format.json { head :ok }
else
render json: {
errors: #course.errors.full_messages
}
end
end
end
end
Keep your form, change strong params to this:
params.require(:courses).permit(
courses: [
:id,
:title,
:start_date,
:end_date
]
)
With this code params should be without index key, #courses is just an array:
# CoursesController
def new
#courses = []
# creating 3 items for example
3.times do
#courses << Course.new
end
end
def create
errors = false
#courses= []
# keep courses in the array for showing errors
courses_params[:courses].each do |params|
course = Course.new(params)
#courses << course
unless course.valid?
errors = true
end
end
if errors
render :new
else
# if no errors save and redirect
#courses.each(&:save)
redirect_to courses_path, notice: 'courses created'
end
end
It turns out that the f.simple_fields_for 'courses[]' ... method only gives that fieldset an ID if the form is populated by an existing record, and the params structure of a string ID mapping to a course hash is only used in this case. For "fresh" records, there is no ID and the course hashes are placed in a plain array.
This bit of code was running in the context of "rolling over" courses from one year to another - copying a previous course and changing the dates. This meant that each fieldset had the ID of the original course.
When the form was submitted, a new record was created and validated with the new attributes, and it was this fresh record with no ID that repopulated the form. The "it only happens when one course is submitted" thing was a red herring - a product of the test scenario.
So worth noting: f.simple_fields_for 'courses[]' ... creates an array for new records and a hash mapping IDs to attributes for existing records.
i am creating a user ACL i have 2 models one is users_group and another is access_sections.
Now let's supouse I have 2 access_sections (pages, users)
I wanted to associate both access_sections with users_group,
Now here what i have already did
I created many to many association, added new join table access_sections_user_groups
Whenever i add new user group I pass instance variable to get all available access_sections the I show them as a checkbox in a new user group form, once i check any of access section I loop through in array and insert all elements in 'table access_sections_user_groups' with users_group id and access_sections id everything is working now i wanted to do that when I edit usergroup my checkbox should be checked if the specific access_sections is associated with user_group
user_group_controller.rb
def create
#user_group = UserGroup.new(group_params)
if #user_group.save
flash[:notice] = "User group added !"
flash[:type] = "success"
if params[:user_group][:access_sections].present?
params[:user_group][:access_sections].each do |f|
UserGroup.find(#user_group.id).access_sections << AccessSection.find(f)
end
end
redirect_to(:action => "index")
else
flash[:notice] = "error while adding new group!"
flash[:type] = "danger"
render("add_new")
end
end
user_group/_form.html.erb
<%= f.label("Add section to Access Control ") %>
<% #acl_sections.each do |k| %>
<%= f.check_box(:access_sections, { :multiple => true }, k.id, nil) %>
<% end %>
Should work
f.check_box(:access_section_ids, { :multiple => true }, k.id, nil)
Hence you need to update your model like this way:
def create
if #user_group.create(user_group_params)
# ...
end
end
def user_group_params
params.require(:user_group).permit(access_section_ids: [])
end
I am using ransack for search in my rails 3.2 application using postgres as database.
I have a Invoice model and every invoice belongs_to a buyer. Below is my search form in index page.
views/invoices/index.html.erb
<%= search_form_for #search do |f| %>
<%= f.text_field :buyer_name_cont %>
<%= f.submit "Search"%>
<% end %>
And here is my controller code.
controllers/invoices_controller.rb
def index
#search = Invoice.search(params[:q])
#invoices=#search.result(:distinct => true).paginate(:page => params[:page], :per_page => GlobalConstants::PER_PAGE )
respond_to do |format|
format.html # index.html.erb
format.json { render json: #invoices }
end
end
Let's say a invoice is there of a buyer having name "Bat Man".
If I search "Bat", I get the invoice in results.
Again if I search "Man", I get the invoice in results.
But if I search "Bat Man", I don't get the invoice in results.
I know it might be something trivial but I am not able to resolve.
Update
When I tried the sql query formed directly in database using pgAdmin, I realized that in database there were multiple spaces in the buyer name, something like "Bat.space.space.space.Man".
Can something be done so that "Bat.space.Man" search also finds "Bat.space.space.space.Man" in results?
You could sanitize your data. For instance with regexp_replace(). Run in the database once:
UPDATE invoice
SET buyer = regexp_replace(buyer, '\s\s+', ' ', 'g')
WHERE buyer <> regexp_replace(buyer, '\s\s+', ' ', 'g');
And sanitize new inserts & updates likewise.
\s .. class shorthand for "white space" (including tab or weird spaces).
The 4th parameter 'g' is for "globally", needed to replace all instances, not just the first.
Ransack not support cont search for multi terms, I solved the requirement my customized way. the details as following:
Add scope to your model:
scope :like_search, ->(column, value) {
keywords = value.to_s.split.map{ |k| "%#{k}%" }
where(Array.new(keywords.size, "#{column} ILIKE ?").join(' AND '), *keywords)
}
in your view. instead of using f.text_field :buyer_name_cont provided by ransack, use normal field helper text_field_tag :buyer_name, params[:buyer_name]
then restrict your ransack in scope:
scope = Invoice.like_search(:name , params[:buyer_name])
#q = scope.ransack(params[:q])