I am building a family album application with Ruby on Rails. I have two main tables: "families" and "members." families has_many members.
I when I create a new "member" of a "family," I am attempting to track that new members relationship to the original member. My app allows you to create one original member, then all other members must be created from existing member's show pages. The idea is to not allow any new members to be added to the family without having a relationship established with an existing member. This will avoid floater members who are not related to anyone else.
I have a column called "parent_id." When a new member is created, you can add the current member's id (form is in member show view) as the parent_id for the new member. This links the records so they can be referenced later.
My question is fairly simple. In a member's show view, I want a table to display all members who have a parent_id == the current member's id. Pretty simple, but I can't get it to work. This is what I have now:
<table class="table table-striped">
<tr>
<td>First</td>
<td>Last</td>
<td>Birthplace</td>
</tr>
<% #members.where("parent_id == #member.id").each do |p| %>
<tr>
<td><%= p.first_name %></td>
<td><%= p.last_name %></td>
<td><%= p.birthplace %></td>
</tr>
<% end %>
</table>
This is the error this code throws:
SQLite3::SQLException: near ".": syntax error: SELECT "members".* FROM "members" WHERE "members"."family_id" = ? AND (parent_id == #member.id)
Here is the show action in my members controller:
def show
#family = Family.find(params[:family_id])
#member = Member.find(params[:id])
#new = Member.new
#members = #family.members
end
Here is the schema for my two tables:
create_table "families", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "members", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "birthplace"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "family_id"
t.integer "parent_id"
t.integer "child_id"
t.integer "sibling_id"
end
You're trying to access to an id attribute within the #members object, which probably is an ActiveRecord_Associations_CollectionProxy from the Member model.
If you make an #members.inspect you'll see every element inside of it, as an array value has an id but not the object itself.
You could get each member id attribute if you iterate over every element and then in that way you're able to make the comparison parent_id = member.id, which could be safer if you do 'parent_id = ?', member.id to avoid SQLi.
But I think you could check easier the members on #members just comparing its attributes:
<% #members.each do |member| %>
<% if member.parent_id == member.id %>
<tr>
<td><%= member.first_name %></td>
<td><%= member.last_name %></td>
<td><%= member.birthplace %></td>
</tr>
<% end %>
<% end %>
Related
Inside a nested form I am using relation table (has_many through:) in collection_select for edit/update methods.
When I use raise params.inspect (to check my params)- inside update method, the params for language_users_attributes contains:
"language_users_attributes"=> {"0"=>{"language_id"=>"5", "_destroy"=>"false", "id"=>"11"}, "1595001921026"=>{"language_id"=>"1", "_destroy"=>"false"}, "1595001921984"=>{"language_id"=>"4", "_destroy"=>"false"}}
Where does the id attribute come from? It is an ID from language_users table, but I did not pass it in, why is it there?
View looks like this:
<tr>
<td><%= f.collection_select :language_id, Language.all, :id, :language, {}, {} %></td>
<td></td>
<td>
<%= f.hidden_field :_destroy %>
<%= link_to 'Delete', '#', class: 'remove_record' %>
</td>
</tr>
DB Schema:
create_table "language_users", force: :cascade do |t|
t.integer "language_id"
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "languages", force: :cascade do |t|
t.string "language"
end
create_table "users", force: :cascade do |t|
t.string "email"
t.string "first_name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["company_id"], name: "index_users_on_company_id"
end
Controller:
def edit
#teacher = Teacher.find(params[:id])
#teacher.languages.build unless #teacher.languages.present?
end
def update
#teacher = Teacher.find(params[:id])
if #teacher.update(teacher_edit_params)
redirect_to teachers_path
else
render :edit
end
end
Private
def teacher_edit_params
params.require(:teacher).permit(:first_name, language_users_attributes: [:id, :language_id, :_destroy])
end
end
To assign a relation with the collection helpers you just need to use the _ids setter and getter created by the has_many/has_and_belongs_to_many methods.
You don't need nested attributes or fields_for.
<%= form_for(#user) do |f| %>
<%= f.collection_select :language_ids, Language.all, :id, :language %>
...
<% end %>
def user_params
params.require(:user).permit(..., language_ids: [])
end
This setter takes an array as input and will automatically create/delete records in the join table.
Nested attributes only need to be used if you are assigning additional attributes to the join table like for example if the user was selecting a language as well as their level of proficiency. This should not be confused with a single select or a series of checkboxes.
In that case the id attribute comes in as its what toggles accepts_nested_attributes between updating existing records or creating new records.
I have been struggling with this problem for much too long and am hoping that one of you will be able to see what I cannot. It has to be a simple stupid error since I do this everywhere in the application with no errors.
Problem: I have a billing_type (reference) table which contains a number of records. I also have a billing table which contains bills. When I display the billing table in a list, the billing_type is show as:
"#<BillingType:0x00000006f49470>"
which I am assuming is its pointer.
The billing_type model :
class BillingType < ActiveRecord::Base
validates :billing_type, presence: true, uniqueness: true
has_many :billings
accepts_nested_attributes_for :billings
end
The billing model :
class Billing < ActiveRecord::Base
belongs_to :billing_type
belongs_to :horse
validates :billing_type_id, presence: true
validates :horse_id, presence: true
end
The schema:
create_table "billing_types", force: :cascade do |t|
t.string "billing_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "billing_types", ["billing_type"], name: "index_billing_types_on_billing_type", unique: true
create_table "billings", force: :cascade do |t|
t.date "billing_date"
t.integer "billing_type_id"
t.integer "horse_id"
t.string "notes"
t.decimal "cost"
t.date "date_billed"
t.date "date_received"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The query in the controller (note: I exported the database and put this in the SQL and it returned everything, including the billing_type, o.k.):
def index
#billings = Billing.find_by_sql("SELECT billings.id, billings.billing_date, horses.horse_name, billings.billing_type_id, billing_types.billing_type, billings.notes, billings.cost, billings.date_billed, billings.date_received FROM billings JOIN horses ON billings.horse_id = horses.id JOIN billing_types ON billings.billing_type_id = billing_types.id ORDER BY billings.billing_date, LOWER(horses.horse_name)")
end
The index page:
.
.
.
<div class = "table-container">
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>Billing date</th>
<th>Horse name</th>
<th>Billing type</th>
.
.
.
</tr>
</thead>
<tbody>
<% #billings.each do |billing| %>
<tr>
<% if billing.billing_date %>
<td><%= billing.billing_date.strftime("%d/%m/%Y") %></td>
<% else %>
<td><%= billing.billing_date %></td>
<% end %>
<td><%= billing.horse_name %></td>
<td><%= billing.billing_type %></td>
.
.
.
</tr>
<% end %>
</tbody>
</table>
Thanking you in advance for any help you can give!
That's exactly what I would expect to see
billing.billing_type is the representation of the the whole BillingType class as an object.
If you wanted that then maybe
billing.billing_type.inspect is what you are expecting but I suspect that what you really want is the name in which case you should be looking to display a property of the object not the object itself. i.e.
billing.billing_type.name
I am trying to render a list of Active Records as follows:
<% #workout_sets.each do |workout_set| %>
<tr>
<td><%= workout_set.reps %></td>
<td><%= workout_set.exercise.name %></td>
<td><%= link_to 'Show', workout_set %></td>
<td><%= link_to 'Edit', edit_workout_set_path(workout_set) %></td>
<td><%= link_to 'Destroy', workout_set, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
My AR setup looks like:
class WorkoutSet < ActiveRecord::Base
belongs_to :workout
belongs_to :exercise, class_name: 'Exercise', foreign_key: 'exercises_id'
end
class Exercise < ActiveRecord::Base
end
class Workout < ActiveRecord::Base
has_many :workout_set
end
and my schema is
create_table "exercises", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "exercises", ["name"], name: "index_exercises_on_name", unique: true
create_table "workout_sets", force: :cascade do |t|
t.integer "reps", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "exercises_id"
t.integer "workouts_id"
end
add_index "workout_sets", ["exercises_id"], name: "index_workout_sets_on_exercises_id"
add_index "workout_sets", ["workouts_id"], name: "index_workout_sets_on_workouts_id"
create_table "workouts", force: :cascade do |t|
t.string "location", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
In attempting to render the page I get the following error
undefined method `name' for nil:NilClass
When I change the path in my template to <%= workout_set.exercise %> it renders each row like 444 #<Exercise:0x007fbde9dde998> Show Edit Destroy which is what I expect.
Why is the the attempted access of the name property causing this error?
One of your WorkoutSet does not have an associated Exercise. You can enforce that a WorkoutSet has an exercise Exercise in your WorkoutSet model but there are implications to that. Mainly, you could not create a WorkoutSet without first creating the Exercise. If that's what you want then add the following to the WorkoutSet model.
validates_presence_of :exercise_id
More likely though, you just want to handle the page crashing when there is no associated Exercise.
<td><%= workout_set.exercise.name unless workout_set.exercise.blank? %></td>
That will give you a blank cell but you can do something like this to have a placeholder.
<td><%= workout_set.exercise.blank? ? "No exercise for this set" : workout_set.exercise.name %></td>
You haven't set up the relationship in the Exercise model
class Exercise < ActiveRecord::Base
has_many :workout_sets
has_many :workouts, through: :workouts_sets #not needed but good to setup
end
or if you're trying to do a 1-to-1 relationship between Exercise and WorkoutSet
class Exercise < ActiveRecord::Base
has_one :workout_set
end
Also having an 's' at the end of the foreign keys in your workout_sets table (i.e. 'workouts_id') is somewhat bad form. I'm pretty sure Rails will be smart enough to make it work but if you run into more bugs I'd try changing those to 'workout_id' and 'exercise_id'.
I currently have a view page named "users". In my view page, I'm displaying a table with attributes pulled from the users table such as the first name, last name, last login, admin, etc... In the admin field, it displays from the users table true or false.
What I would like to accomplish is to create a drop down, that displays the user's current value for admin (true or false), and then be able to change this attribute from the view page. Currently, I'm having to use the console on the server to manually change a user's value to true or false to grant or revoke admin privileges. Needless to say, this is inconvenient. I'm still fairly new to Rails and have been using scaffolds up to this point. I'm not sure how to accomplish this, and any advise would be greatly appreciated!
Users schema:
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin", default: false
t.string "first_name"
t.string "last_name"
end
Admin controller:
class AdminController < ApplicationController
before_action :authenticate_user!
def users
#users = User.all.order('last_name ASC')
end
def reports
end
end
Users view page:
<table class="table table-hover table-condensed">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Email</th>
<th>Creation Date</th>
<th>Last Sign In</th>
<th>IP Address</th>
<th>Admin</th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.last_name %></td>
<td><%= user.first_name %></td>
<td><%= user.email %></td>
<td><%= user.created_at.strftime("%-m/%-d/%y") %></td>
<td><%= user.current_sign_in_at.strftime("%-m/%-d/%y") %></td>
<td><%= user.current_sign_in_ip %></td>
<td><%= user.admin %></td>
</tr>
<% end %>
</tbody>
</table>
You could accomplish this in a couple of different ways. For example, you could let the link in the drop-down trigger a method toggle_admin for removing admin if user is admin or the reverse if that's the case.
First create the route, in routes.rb:
resource :users do
get :toggle_admin
end
in your view:
<%=link_to (#current_user.admin ? 'Remove admin' : 'Make admin'), toggle_admin_users_path %>
And in your controller, you could have something like this:
def toggle_admin
#current_user.update_attribute :admin, (#current_user.admin ? false : true)
end
Hmm... yeah, I don't know. Could work. As you can see, I assume here that you have a #current_user variable for picking up the relevant user, otherwise send in the user.id into these methods, either from the url params or directly into the controller method.
First you'll need a route and a corresponding controller action that handles the update.
In a RESTful way this would be in routes.rb: resources :users, only: [:update]
In your view you need a from, that sends the data to the controller:
<%= form_for user do %>
...
Each of the users in the #users.each should have it's own form and a submit button. To create the select (that's the dropdown with true/false value) use the Rails form helper.
The controller action could look something like this:
def update
user = User.find(params[:id])
user.update_attributes(user_params)
end
private
def user_params
params.require(:user).permit(:admin)
end
Schema:
create_table "reports", :force => true do |t|
t.integer "user_id"
t.string "apparatus"
t.string "capt"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "login", :limit => 40
t.string "name", :limit => 100, :default => ""
t.string "email", :limit => 100
t.string "crypted_password", :limit => 40
t.string "salt", :limit => 40
t.datetime "created_at"
t.datetime "updated_at"
t.string "remember_token", :limit => 40
t.datetime "remember_token_expires_at"
t.string "rank"
t.integer "shift"
t.integer "access"
end
user model:
Class User < ActiveRecord::Base
has_many :reports
# bunch of other stuff thats not important
end
report model:
Class Report < ActiveRecord::Base
belongs_to :user
end
views/reports/index
<% #reports.each do |report| %>
<tr>
<td><%= report.user_id %></td> # ****THIS IS THE LINE IN QUESTION****
<td><%= report.apparatus %></td>
<td><%= report.capt %></td>
<td><%= report.body %></td>
<td><%= link_to 'Show', report %></td>
<td><%= link_to 'Edit', edit_report_path(report) %></td>
<td><%= link_to 'Destroy', report, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
I would like to be able to display the name of the user that created the report. I was under the assumption that declaring the belongs_to and has_many associations would make this possible by writing report.user.name or something like that. Where have I gone wrong?
I'm 99% sure it's because one or more of your reports do not have an associated user. Try
<%= report.user.name rescue "none" %>
When there is no value in user_id field on a report then report.user will return nil. So report.user.name would be like calling nil.name, which raises an error.
UPDATE: here's a better way:
<%= report.user.try(:name) %>
You can do:
<%= report.user.name %>
But for efficiency, in your controller you can do a join to get the users name in the same query used to fetch #reports.
This query might look something like:
#reports = Report.select("reports.id as id, reports.apparatus as apparatus, reports.capt as capt, reports.body as body, users.name as user_name").joins("LEFT JOIN `users` ON `users`.`id` = `reports`.`user_id`")
Then your output would look like:
<%= report.user_name %>