RAILS: Insert data into Join Model comes with unexpected behaviour - sql

I don't quite understand why after inserting data in my join model, they came out with strange results.
Here are my models
StaffRateItem
class StaffRateItem < ActiveRecord::Base
has_many :staff_default_rate_settings
has_many :assignment_type_categories, through: :staff_default_rate_settings do
def create_staff_default_rate_setting(assignment_type_category, rates_amount_attr)
StaffDefaultRateSetting.create(rates_amount: rates_amount_attr){ self << assignment_type_category}
end
end
accepts_nested_attributes_for :staff_default_rate_settings
end
AssignmentTypeCategory
class AssignmentTypeCategory < ActiveRecord::Base
has_many :staff_default_rate_settings
has_many :staff_rate_items, through: :staff_default_rate_settings
end
StaffDefaultRateSetting
class StaffDefaultRateSetting < ActiveRecord::Base
belongs_to :staff_rate_item
belongs_to :assignment_type_category
end
Then under my StaffDefaultRateSetting Controller create method
def create
#staff_rate_item = StaffRateItem.new(staff_rate_item_params)
#assignment_type_category1 = AssignmentTypeCategory.find(params[:staff_rate_item][:staff_default_rate_setting][0][:assignment_type_category_id])
#assignment_type_category2 = AssignmentTypeCategory.find(params[:staff_rate_item][:staff_default_rate_setting][1][:assignment_type_category_id])
#assignment_type_category3 = AssignmentTypeCategory.find(params[:staff_rate_item][:staff_default_rate_setting][2][:assignment_type_category_id])
#staff_rate_item.assignment_type_categories.create_staff_default_rate_setting #assignment_type_category1, params[:staff_rate_item][:staff_default_rate_setting][0][:rates_amount]
#staff_rate_item.assignment_type_categories.create_staff_default_rate_setting #assignment_type_category2, params[:staff_rate_item][:staff_default_rate_setting][1][:rates_amount]
#staff_rate_item.assignment_type_categories.create_staff_default_rate_setting #assignment_type_category3, params[:staff_rate_item][:staff_default_rate_setting][2][:rates_amount]
respond_to do |format|
if #staff_rate_item.save
format.html { redirect_to staff_default_rate_settings_url, notice: 'Staff default rate setting was successfully created.' }
format.json { render action: 'show', status: :created, location: #staff_default_rate_setting }
else
format.html { render action: 'new' }
format.json { render json: #staff_default_rate_setting.errors, status: :unprocessable_entity }
end
end
end
Basically StaffDefaultRateSetting is the join model for the StaffRateItem and AssignmentTypeCategory models.
Thus under its physical table, it has its following fields.
id
staff_rate_item_id
assignment_type_category_id
rates_amount
created_at
updated_at
So what I'm saying that each time I insert a new staff_rate_item record, I expect to include three other specific assignment_type_category fields for that staff_rate_item record, along with their respective rate amounts. Thus the end result in the StaffDefaultRateSetting table is that I will see three records in there.
But instead I get the following result.
id | staff_rate_item_id | rates_amount | created_at | updated_at | assignment_type_category_id |
18 | | 20.00 | 2014-04-08 22:57:25.81432 | 2014-04-08 22:57:25.81432 | |
19 | | 20.00 | 2014-04-08 22:57:25.888068 | 2014-04-08 22:57:25.888068 | |
20 | | 20.00 | 2014-04-08 22:57:25.915939 | 2014-04-08 22:57:25.915939 | |
21 | 6 | | 2014-04-08 22:57:26.016725 | 2014-04-08 22:57:26.016725 | 1 |
22 | 6 | | 2014-04-08 22:57:26.021352 | 2014-04-08 22:57:26.021352 | 2 |
23 | 6 | | 2014-04-08 22:57:26.024253 | 2014-04-08 22:57:26.024253 | 3 |
If you look the rows carefully, I got 6 records instead of supposedly 3 records only. The rates amount were inserted into three separate rows whilst the rows with staff_rate_item_id and assignment_type_category_id are inserted independently - without the rate amounts!
This is clearly very wrong behaviour.
I've been search a lot for online to determine if there's something wrong with my model creation logic on StaffDefaultRateSetting layer.
def create_staff_default_rate_setting(assignment_type_category, rates_amount_attr)
StaffDefaultRateSetting.create(rates_amount: rates_amount_attr){ self << assignment_type_category}
end
Or maybe there's the basic setup of inserting join model data I failed grasp (and oughta know). Surely it must be something basic code to insert such simple join model creation. What did I do wrong here? I'm using PostgreSQL for development, not MySQL just fyi.
Any ideas would be greatly appreciated!

After spending sometime thinking about the problem, I went back to the drawing board and redo the basics again.
This time. I redo the code in the StaffDefaultRateSetting Controller
def create
#staff_rate_item = StaffRateItem.new(staff_rate_item_params)
#assignment_type_category1 = AssignmentTypeCategory.find(params[:staff_rate_item][:staff_default_rate_setting][0][:assignment_type_category_id])
#assignment_type_category2 = AssignmentTypeCategory.find(params[:staff_rate_item][:staff_default_rate_setting][1][:assignment_type_category_id])
#assignment_type_category3 = AssignmentTypeCategory.find(params[:staff_rate_item][:staff_default_rate_setting][2][:assignment_type_category_id])
#staff_rate_item.staff_default_rate_settings.create(assignment_type_category_id: #assignment_type_category1.id, rates_amount: params[:staff_rate_item][:staff_default_rate_setting][0][:rates_amount] )
#staff_rate_item.staff_default_rate_settings.create(assignment_type_category_id: #assignment_type_category2.id, rates_amount: params[:staff_rate_item][:staff_default_rate_setting][1][:rates_amount] )
#staff_rate_item.staff_default_rate_settings.create(assignment_type_category_id: #assignment_type_category3.id, rates_amount: params[:staff_rate_item][:staff_default_rate_setting][2][:rates_amount] )
respond_to do |format|
if #staff_rate_item.save
format.html { redirect_to staff_default_rate_settings_url, notice: 'Staff default rate setting was successfully created.' }
format.json { render action: 'show', status: :created, location: #staff_default_rate_setting }
else
format.html { render action: 'new' }
format.json { render json: #staff_default_rate_setting.errors, status: :unprocessable_entity }
end
end
end
I took out my def create method in StaffRateItem Model and I felt the rewritten code should be suffice to do the same task without worrying about the major hassles of trying fancy code to make the save data to work as I'm pressed for times to get my client's project underway. Cheers!

Related

How to filter through controller and action within params column?

I am trying to filter through controller and action to find the record's activity:
I initially queried the record like this:
SELECT * FROM user_activity_logs WHERE user_id= 1234;
It returned me this:
id | user_id | ip_address | controller | action | params
__________________________________________________________
1 1234 blah dashboard index {"controller"=>"dashboard","action"=>"index"}
What I in fact want to query is to filter through controller and action within the params column. Lets say I want to filter by specifying controller to be "users" and action to be "update".
I want to know what I can get based on these two params and a given user_id.
UPDATED: Here is the action method definition for update, but do not know if it helps. All I want is to find user_id 1234 who has "users" value for controller and "update" value for action inside params.
def update
run(action: :update, model_variable: :#user) do |result|
respond_to do |format|
if result['success']
format.html { redirect_to(user_edit_demographics_path(result['model'])) }
format.json { head :no_content }
else
format.html do
render action: 'select_type'
end
format.json do
render json: result['model'].errors,
status: :unprocessable_entity
end
end
end
end
end

Rails group_by query with parent & child records

I am trying to get all the users who may Buy Sell or Create by counting the time like the below
I want to like this below example
2021 (0)
2020 (0)
2019 (4)
January (2)
-> Buy (1) // Specifically with records date if possible
-> Sell (1)
August (1)
-> Create (1)
September (1)
-> Buy (1)
2018 (3)
January (2)
-> Buy (1) // Specifically with records date if possible
-> Sell (1)
August (1)
-> Create (1)
And so on........
And I was trying something like below
// controller
#user = User.find(params[:user_id])
#records = #user.records.joins(:task)
.where(tasks: {name: ["Buy", "Sell", "Create"]})
.group_by {|t| t.created_at.beginning_of_year}
// view.html.erb
<% #records.each do |y, h| %>
<%= y.strftime("%Y") %>
<% h.each do |f| %>
<%= f.task.name %>
<% end %>
<% end %>
Output like below
2018
Buy
Sell
here are below my models & tables, I was trying several ways but still didn't get that.
//table users
has_many :records, dependent: :destroy
--------------------
id | name | etc... |
--------------------
1 | john | etc |
--------------------
2 | alex | etc |
--------------------
//table tasks
has_many :records, dependent: :destroy
-------------
id | name |
-------------
1 | Buy |
-------------
2 | Sell |
-------------
3 | Create |
-------------
//table records
belongs_to :task, required: true
belongs_to :user, required: true
-------------------------------------
id | user_id | task_id | created_at |
-------------------------------------
1 | 2 | 2 | timestamps |
-------------------------------------
2 | 1 | 1 | timestamps |
-------------------------------------
3 | 1 | 2 | timestamps |
-------------------------------------
Thanks in Advance
instead of querying the user query the records table directly with the user_id and the task names then perform 3 group_by(s) first group_by is on the year the
the task was created the second group_by is for the month and the third is for the task name
The code to look up the task
records = Record.joins(:user, :task).where(users: {id: #user.id}, tasks: {name: ["Buy", "Sell", "Create"]})
records_grouped_by_year = records.group_by { |e| e.created_at.year }.sort.to_h
# now you will get a list of tasks in a hash with keys as years { 2018 => [record1, record2, record3, record4]}
records_grouped_by_year_month = records_grouped_by_year.transform_values { |records| records.group_by { |r| r.created_at.strftime("%B") }) }
# now you will get a hash inside a hash first key is year second key is month the record was created in { 2018 => {"January"=>[record1, record2, record4], "August" => [record3]}
records_grouped_by_year_month_name = records_grouped_by_year_month.transform_values { |records| records.transform_values { |record| tasks.group_by { |r| r.taks.name } } }
# now you will get 3 nested hashes first key is year second key is month and third key is the record name { 2018 => {"January"=>{"Sell=>[record1, record2], "Buy=>[record4]}, "August" => { "Buy" => [record3]}}}
(records.key.first..Time.current.year).each { |year| records_grouped_by_year_month_name[year] ||= {} }
# now it will add an empty hash for each year that didn't exists in the records
In your view:
<% task_count_grouped_by_year_grouped_by_name.each do |year, data_by_month| %>
<h1> <%= year %> </h1>
<% if data_by_month.blank? %>
<%= there was not sale on this year %>
<% else %>
<% data_by_month.each do |month, data_by_name| %>
<h2> <%= month %> </h2>
<% data_by_name.each do |task_name, records| %>
<br /> <bold><%= task_name %></bold> -> (<%= records.count %>) <% #
you have the records here as an array so you can do whatever you want with them %>
<% end %>
<% end %>
<% end %>
<% end %>

How to get associated resources sorted in a specific way in a Ruby on Rails API

I have to model classes: TBMs and Locations
TBMs is the parent table and locations is the child table.
I have a fully developed RoR app for which I want to developed an API.
I started to develop an API/V1 controller for TBMs.
class Api::V1::TbmsController < Api::V1::BaseController
before_filter :authenticate_user!
before_action :set_tbm, only: [:show, :update]
def index
tbms = Tbm.order(:id)
render json: tbms
end
private
def set_tbm
#tbm = Tbm.find(params[:id])
end
def tbm_params
params.require(:tbm).permit(:tbm_name, :chainage_direction).delete_if{ |k,v| v.nil? }
end
end
I would like the index method when retrieves the two TBMs to include also the locations for each of them from child table Locations and sorted by daily date descending (locations to be enumerated starting with the most recent date).
The TBMs are already sorted ascending by their IDs (as shown in the returned JSON).
Here is the structure of my tables:
tbms
Table "public.tbms"
Column | Type | Modifiers
--------------------+-----------------------------+---------------------------------------------------
id | integer | not null default nextval('tbms_id_seq'::regclass)
created_at | timestamp without time zone |
updated_at | timestamp without time zone |
tbm_name | character varying(255) |
chainage_direction | character varying(255) |
Indexes:
"tbms_pkey" PRIMARY KEY, btree (id)
"index_tbms_on_tbm_name" UNIQUE, btree (tbm_name)
locations
Table "public.locations"
Column | Type | Modifiers
------------+-----------------------------+--------------------------------------------------------
id | integer | not null default nextval('locations_id_seq'::regclass)
tbm_id | integer |
created_at | timestamp without time zone |
updated_at | timestamp without time zone |
daily_date | date | not null
station | numeric(10,3) | not null
tbm_status | character varying(255) | not null
latitude | numeric(12,8) |
longitude | numeric(12,8) |
Indexes:
"locations_pkey" PRIMARY KEY, btree (id)
"index_locations_on_tbm_id_and_daily_date" UNIQUE, btree (tbm_id, daily_date)
"index_locations_on_tbm_id" btree (tbm_id)
"index_locations_on_tbm_id_and_station" btree (tbm_id, station)
The curl command is:
curl -i -X GET \
-H "Authorization:Token token=\"mytoken\", email=\"myemail#gmail.com\"" \
-H "Content-Type:application/json" \
'http://localhost:3000/api/v1/tbms/'
And the JSON returned is:
{
"tbms":[
{
"id": 1,
"tbm_name": "Denis",
"chainage_direction": "Ascending",
"created_at": "2014-11-13T19:00:00-05:00",
"updated_at": "2015-11-16T17:36:10-05:00"
},
{
"id": 2,
"tbm_name": "Lea",
"chainage_direction": "Ascending",
"created_at": "2014-11-13T19:00:00-05:00",
"updated_at": "2015-11-04T15:27:54-05:00"
}
]
}
I am testing my API with Chrome extension DHC by Restlet.
The solution relies on the locations controller and nested routes:
#api
namespace :api do
namespace :v1 do
resources :users, only: [:index, :create, :show, :update, :destroy]
resources :sessions, only: [:create]
resources :tbms, only: [:index, :edit, :update, :show] do
resources :locations, only: [:index, :show]
end
resources :locations, only: [:index, :create, :show, :update, :destroy]
end
end
And changing the locations controller to accept parameters for the index method:
class Api::V1::LocationsController < Api::V1::BaseController
before_filter :authenticate_user!
before_action :set_location, only: [:show, :update, :destroy]
def index
if params[:tbm_id]
locations = Location.where("tbm_id = '#{params[:tbm_id]}'").order("daily_date DESC")
else
locations = Location.order("daily_date DESC, tbm_id")
end
#locations = apply_filters(locations, params)
render json: locations
end
def show
render json: #location
end
def update
if #location.update(location_params)
render json: #location, status: 200, location: api_v1_location_path(#location.id)
else
return api_error(status: 422, errors: #location.errors)
end
end
def create
location = Location.new(location_params)
if location.save!
render json: location, status: 201, location: api_v1_location_path(location.id)
else
return api_error(status: 422, errors: location.errors)
end
end
def destroy
if #location.destroy
head status: 204
else
return api_error(status: 422, errors: #location.errors)
end
end
private
def set_location
#location = Location.find(params[:id])
end
def location_params
params.require(:location).permit(:tbm_id, :daily_date, :station, :tbm_status, :latitude, :longitude).delete_if{ |k,v| v.nil? }
end
end
And generating this kind of HTTP request:
curl -i -X GET \
-H "Authorization:Token token=\"mytoken\", email=\"myemail#gmail.com\"" \
-H "Content-Type:application/json" \
'http://localhost:3000/api/v1/tbms/1/locations/'
Problem solved.

Trouble with Paperclip and Ajax with Rails 3.2.8

I am writting an application 100% ajax with rails 3.2.8
Everything was going great until I tried to upload photos of the employees to the employee management module.
This is part of my code:
At the Controller:
class EmpleadosController < ApplicationController
respond_to :js
before_filter :require_user
def index
#empleados = Empleado.paginate( page: params[ :page ],
joins: [:user,:contacto],
select: 'empleados.id as id, empleados.user_id, empleados.contratado_el, empleados.despedido_el, empleados.nss, empleados.sueldo_base_semanal, empleados.comentarios, empleados.foto_file_name, empleados.foto_content_type, empleados.foto_file_size, empleados.foto_updated_at, users.username as username, contactos.salutacion_id as salutacion_id, contactos.id as contacto_id, contactos.nombres as nombres, contactos.apellidos as apellidos'
).sorted( params[ :sort ] )
end
def new
#empleado = Empleado.new
end
def create
#empleado = Empleado.new(params[:empleado])
#empleado.sueldo_base_semanal = params[:empleado][:sueldo_base_semanal].gsub(',','').gsub('$','').to_f
if #empleado.save
redirect_to empleados_path
else
render action: 'new'
end
end
...
Now for the view (I am using HAML):
= form_for( #empleado, remote: true, html: {multipart: true} ) do |f|
= show_me_the_errors #empleado
%table.captura
%tr
%td.etiqueta
= f.label :user_id
%td.campo
= f.select :user_id,
User.usuarios_no_asignados,
{ include_blank: true },
{ tabindex: 1 }
= image_tag 'nuevo_24.png',
style: 'vertical-align: bottom;',
id: 'empleado_nuevo_usuario'
... ( 5 more text fields ) ...
%tr
%td.etiqueta
= f.label :foto
%td.campo
= f.file_field :foto,
accept: 'image/png,image/gif,image/jpeg'
The thing is that while there is not selected file for upload, everything works fine, the command respond_to :js works as it should be in all interactions between create with index and new.
But, when you select an image, all the interactions after create with index and new become HTML completely ignoring the respond_to :js, I mean, the form for behaves just like if the remote: true wasn't there
When everything works well, the URL is localhost:3000 and never changes, but when I select an image to upload, the URL after crete becomes localhost:3000/empleados
Does any one have any clue about this? I have been trying to solve this for the last 3 dyas and failed.
Thaks in advance.
OK, after several days trying to find the problem I started working on a workaround. So this is it:
1) I created a DB Table for temprary storage, with the ID field set without autoincrement, and with the normal field names:
mysql> describe imagens;
+---------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| imagen_file_name | varchar(500) | YES | | NULL | |
| imagen_content_type | varchar(500) | YES | | NULL | |
| imagen_file_size | int(11) | YES | | NULL | |
| imagen_updated_at | datetime | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
| lock_version | int(11) | NO | | 0 | |
+---------------------+--------------+------+-----+---------+-------+
8 rows in set (0.00 sec)
2) at the view (_form.html.haml):
-# this variable will be used as primary key for identifying the image to upload in the database
- ale = Time.now.to_i
3) at the view (_form.html.haml):
I separated the file_field tag
-# FORM for general data input
= form_for( #empleado, remote: true ) do |f|
...
then, inside that form I add, a hidden file with the variable ale and a pretty small iframe
-# I use it in order to find out for wich record to search at the temporary DBTable
= hidden_field_tag :ale, ale
-# this IFRAME will be used to catch the error message returned by rails when uploading the image, it will be invisible
%iframe{src: '#', frameborder:0, height: 0, width: 0, id: 'nulo', name: 'nulo'}
and encapsulated in its own form_for tag, but without any kind of submit or commit button.
= form_for( #imagen, remote: true, html: {multipart: true, target: 'nulo'} ) do |f|
-# used to set the primary key to this value
= hidden_field_tag :ale, ale
= f.file_field :imagen, {style: 'vertical-align: middle;'}
then a little javascript to upload the image when the user choose one, it does the submit by an event trigger:
:javascript
$('#imagen_imagen').change(function(){
$('#new_imagen').submit();
});
4) this is the content of the model for imagen (models/imagen.rb):
class Imagen < ActiveRecord::Base
attr_accessible :imagen
has_attached_file :imagen,
styles: { t100: '100x100>', t300: '300x300X', t600: '600x600' },
convert_options: { all: '-strip' },
path: ":rails_root/public/system/:class/:attachment/:id/:style/:filename",
url: "/system/:class/:attachment/:id/:style/:filename",
hash_secret: 'topsecret'
validates_attachment_size :imagen,
less_than: 250.kilobytes,
message: (I18n.t :mensajes)[:paperclip][:grande]
end
5) this is the code at controllers/imagens_controller.rb :
class ImagensController < ApplicationController
respond_to :js
# by the way, this sline is from authlogic, a great gem to control the access to your site
before_filter :require_user
def create
#imagen = Imagen.new(params[:imagen])
#imagen.id = params[:ale]
#imagen.save
render text: ''
end
end
6) now, in controller/empleados_controller.rb the code is:
def create
#empleado = Empleado.new(params[:empleado])
if #empleado.save
imagen = Imagen.find(params[:ale].to_i)
if imagen
#empleado.foto = imagen.imagen
#empleado.save
imagen.destroy
end
redirect_to empleados_path
else
#imagen = Imagen.new
render action: 'new'
end
end
7) Glosary:
imagen = image
empleado = employee
empleados = staff
ale = short name for aleatorio, I mean, random
foto = picture o photograpy
8) Conclussion:
It works !!!
9) To-Do:
The drawback is that after you upload an image, it will be stored at the database, and then you can cancel the form or change page, that will leave the record and picture alive.
At the beginning I will make a link in my admin module that destroys all records from the Table holding the temporary images, with something like Imagen.all.each{|x| x.destroy }
Later on, I will write a script that at 2:00 a.m. executes that code.

Implement a Rails 3 nested form with has-many through association

I'm trying to implement a Rails3 nested form with has-many through association.
My model relationships are as follows (My models are Project ProjectDuration, ProjectFee). Project can have many project duration through project_fees.
Following are my models/table
mysql> desc projects;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
class Project < ActiveRecord::Base
has_many :project_durations, :through => :project_fees
has_many :project_fees
accepts_nested_attributes_for :project_fees
end
mysql> desc project_durations;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| duration | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
class ProjectDuration < ActiveRecord::Base
has_many :projects, :through => :project_fees
has_many :project_fees
end
mysql> desc project_fees;
+---------------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| projec_id | int(11) | YES | | NULL | |
| project_duration_id | int(11) | YES | | NULL | |
| fee | float | YES | | NULL | |
+---------------------+----------+------+-----+---------+----------------+
class ProjectFee < ActiveRecord::Base
belongs_to :projects
belongs_to :project_durations
end
And my projects_controllers new action is as follows
class ProjectsController < AdminsController
def new
#project = Project.new
#project_durations = ProjectDuration.find(:all)
project_fees = #project.project_fees.build()
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #project }
end
end
end
And I finally I have the following view (new.erb)
<%= form_for(#project, :html => { :class => :form }) do |f| -%>
<% #project_durations.each do |duration| %>
<%= f.fields_for :project_fees do |builder| %>
<%= render 'fee_fields', :f => builder, :project => #project, :duration => duration %>
<% end %>
<% end %>
<% end -%>
and 'fee_fields' is as
<ul>
<li>
<%= duration.duration %>
<%= f.text_field :fee %>
<%= f.hidden_field :project_duration_id, :value => duration.id %>
</li>
</ul>
Even though this saves data to 'project_fees' table, it does not save data in the project_id field of the project_fees table.
I'm using Rails 3 with Ruby 1.8.7 on Linux.
Assuming your MySQL output is cut and pasted, you probably have a typo in a migration somewhere. The column
projec_id
in your project_fees table should be
project_id
This was the life saver for me
http://iqbalfarabi.net/2011/01/20/rails-nested-form-with-has-many-through-association/