Two models I have created i.e "User" and "Communication", and schema is below:
user model scheme:
id | name | Email | SkillType | user_Need
1 | ijk | ijk#ijk.com | Mentor | Ap Chinese
2 | mno | mno#mno.com | Protege | Ap Biology
Communication model schema:
id | UserID | CommunicationMode | CommunicationDetail
1 | 1 | Phone | 1234567890
2 | 1 | Email | ijk#ijk.com
In User model i have has_many relation with Communication, and in Communication model i have belongs_to :user I am adding communication preferences when user sign ups to the application, and in setting, i am trying to display that user's communication preferences in separate controls.when user submits the settings page, i want to delete each record in Communication that is related to current_user, its working fine and the code is below
Communication.delete_all(:UserID => current_user.id)
But when I update them from new action in controller, the records are not updating from Communication. and below is my "new" action code:
def create
#user_basic=User.find(current_user.id)
#students=Students.all
#entrepreneurs=Entrepreneurs.all
#veterans=Veterans.all
#user_communication=Communication.where(:UserID => current_user.id)
Communication.delete_all(:UserID => current_user.id)
#user_communication.update_attributes(:CommunicationMode => "CommunicationDetail")
render 'new'
end
And I am getting below error at this line '#user_communication.update_attributes(:CommunicationMode => "CommunicationDetail")'
Error:
undefined method `update_attributes'
Kindly suggest me where I make mistake, waiting for reply. Thanks
I don't get what you are trying to do,
#user_communication=Communication.where(:UserID => current_user.id)
Communication.delete_all(:UserID => current_user.id)
#user_communication.update_attributes(:CommunicationMode => "CommunicationDetail")
you delete the communications for the given user and then you try to update his attributes with CommunicationMode = "CommunicationDetail".
So, the update fails because there is no record.
I am almost sure that there is a problem with the logic you try to apply.
Your syntax is incorrect
.destroy_all removes the elements you've specified. The problem is you're then trying to call the exact same elements with .update_attributes
You'd need something like this:
def create
#user_basic = User.find(current_user.id)
#students = Students.all
#entrepreneurs = Entrepreneurs.all
#veterans = Veterans.all
#user_communication = #user_basic.communications #-> based on has_many / belongs_to assoc
#user_communication.update_all(CommunicationMode: "CommunicationDetail")
render 'new'
end
If you want to just update records, you don't need to remove them first. You can just use update_attributes to insert the new data into the db
Related
I'm writting an app that manages appointments to different services. Each service "capacity" is determined by one or more Timetables, meaning that service A may have 2 "desks" form June 1 to June 30 while having only 1 from July 1 to August 31, so I can create 2 appointments for '2020-06-03 9:00' but only 1 for '2020-07-03 9:00'.
Everything is modeled just right and I have a custom validator for Appointments on create that checks the cardinality but that isn't enough to prevent two users creating the last available appointment at the same time is it?
How can I control the correct cardinality of this kind of relation without blocking the whole Appointments table?
Appointment creation is done in one place and one place only in the code, in Appointment.create_appointment(params) , is there a way to make that method locked in rails?
There are several ways to implement such restrictions, and the best is to let the database handle hard constrains.
Option one
Considering you have two models, Timetable and Appointment, add available_slots integer column to the Timetable model and decrease that number upon appointment creation and let the database raise an exception if that number goes below zero. In this case, Postgress will lock the column while updating it at the same time, preventing race conditions.
So Timetable could look like:
+----+--------------+--------------+-----------------+
| ID | time_from | time_to | available_slots |
+----+--------------+--------------+-----------------+
| 1 | '2020-03-21' | '2020-04-21' | 2 |
| 2 | '2020-04-22' | '2020-05-21' | 3 |
+----+--------------+--------------+-----------------+
In MySQL, you would make it an unsigned integer, but since Postgres doesn't support it, you have the option to add a positive number check constrain to the available_slots column:
Pure SQL:
ALTER TABLE timetables ADD CONSTRAINT available_slots CHECK (available_slots > 0)
A migration will look like:
class AddPositiveConstraintToTimetable < ActiveRecord::Migration[6.0]
def self.up
execute "ALTER TABLE timetables ADD CONSTRAINT available_slots CHECK (available_slots > 0)"
end
def self.down
execute "ALTER TABLE timetables DROP CONSTRAINT available_slots."
end
end
Add to Appointment model the logic that will decrease available_slots:
belongs_to :timetable
before_create :decrease_slots
def decrease_slots
# this will through an exception from the database
# in case if available_slots are already 0
# that will prevent the instance from being created.
timetable.decrement!(:available_slots)
end
Catch the exception from AppointmentsController:
def create
#appointment = Appointment.new(appointment_params)
# here will be probably some logic to find out the timetable
# based on the time provided by the user (or maybe it's in the model).
if #appointment.save
redirect_to #appointment, notice: 'Appointment was successfully created.'
else
render :new
end
end
Option two
Another way to do it is to add a new model, for example, AvailableSlot that will belong to Appointment and Timetable, and each record in the table will represent an available slot.
For example, if Timetable with id 1 will have three available slots, the table will look like:
Timetable.find(1).available_slots
+----+---------------+
| ID | timetable_id |
+----+---------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+----+---------------+
Then add a unique index constrain to the available_slot_id column in the appointments table:
add_index :appointments, :available_slot_id, unique: true
So every time you create an appointment and associate it with an available slot, the database will, through an exception, if there is a record with the same available_slot_id.
You will have to add logic to find an available slot. A raw example in Appointment model:
before_create :find_available_slot
def find_available_slot
# first find a timetable
timetable = Timetable.where("time_from >= ? AND time_to <= ?", appointment_time, appointment_time).first
# then check if there are available slots
taken_slots = Appintment.where(timetable.id: timetable.id).size
all_slots = timetable.available_slots.size
raise "no available slots" unless (all_slots - taken_slots).positive?
# huzzah! there are slots, lets take the last one
self.available_slot = timetable.available_slots.last
end
That code can be simplified if you add a status column to available_slots that will be changed when an appointment is created, but I leave it to you to figure that out.
These options are based on similar approaches that I've seen on a production Rails applications with a lot of concurrent transactions going on (millions per day) that could cause raise conditions.
I'm trying to make a database that keeps track of items for a user.
For example, a user might want to keep track of their cars and the color, make and year of it. They will first create a cars "model", and they can now add as many new cars as they want. When they make a car, they will be prompted to input the color, make and year as well as generic item information.
The user might also want to keep track of their cats. But the cat "model" has different fields than the car "model." Maybe they need to keep track of the weight and breed of their cats. When they want to add an item of type cat to the database, I will prompt them to input the weight and breed NOT color, make and year.
I'm not very experienced with rails or SQL, but I was thinking when a user tells me they want to keep track of cars, I could create a cars table with color make and year. When they input a car, I would store the car as a generic item and also keep a reference to the cars table where I would store the color make and year. Not quite sure how I would implement though, so I'm open to any new ideas. Thanks in advance for your help.
You are quite right that you will need to create a table for both a car and cat, each with their own columns.
Rails comes with Active Record to facilitate the creation of databases. Active record is really easy to use and has some great documentation.
For example, to create a table for a car, you will have to create a car model first.
To get Active Record to create a model, you just use the following command in terminal:
$ rails g model Car color:string make:string year:integer
This model is stored in app/models. Don't forget to migrate your database after you create the model.
In order for a user to enter the properties of the instance, you will need to use form_for. Documentation for a form_for can be found here. To use a form_for you need to declare the instance for the model that you want to change and the method you are going to use once the form has been submitted. You then follow this up with some input fields which are all associated with a property of the instance. The example in the documentation is a pretty good one.
<%= form_for #article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body, size: "60x12" %>
<%= f.submit "Create" %>
<% end %>
I've been playing with something called Meta models - whereby you have app/models/meta.rb which allows you to invoke models the user defines in the db.
I've had this running, but it's not super efficient.
I'll tell you about it; maybe it will give you some ideas.............
--
Each model can be stripped down to set of 2 common attributes - title & value:
#app/models/node.rb
class Node < ActiveRecord::Base
# columns id | type | title | value | created_at | updated_at
end
This is the very base of the models you wish to use (car / cat etc).
For example, you could make a node have the following stats:
# id | type | title | value | created_at | updated_at
# 1 | Meta::Cat | Greg | A new cat we got | 15/12/2015 | 15/12/2015
# 2 | Meta::Cat | Pip | A little kitten | 16/12/2015 | 16/12/2015
# 3 | Meta::Car | F480 | The red Ferrari | 10/12/2015 | 10/12/2015
This will give you a base set of data which you can then append different associations to.
And yes, the new associations can be stored in the node datatable too.
So in fact, you could have the following:
#app/models/meta.rb
class Meta
classes = %w(cat car)
if classes.any?
classes.each do |klass| #-> "class" is reserved
self.const_set klass.titleize, Class.new(Node) do
belongs_to :user
end
end
end
end
This will programmatically create the Cat and Car classes, which can be called with Meta::Cat etc as required.
I guess this is how you could get your different "base" models.
If you then wanted to add extra associations to that, you'd have to have extra data to make sure that you know which associations each model has.
I've not done this yet, but as you can see above, you can invoke a block which basically allows you to define different things in the pseudo-class:
--
So if you had another meta class called Option, you could use it to set the associations for each other class:
# 2 | Meta::Option | association | has_many :x | 16/12/2015 | 16/12/2015
# 3 | Meta::Option | association | belongs_to :y | 10/12/2015 | 10/12/2015
I still need to think about how it will match the association, but in short, you'll be able to do the following:
#app/models/meta.rb
class Meta
classes = %w(cat car)
if classes.any?
classes.each do |klass| #-> "class" is reserved
self.const_set klass.titleize, Class.new(Node) do
#-> Node.where(name: "association").pluck(:value)
end
end
end
end
This is known as metaprogramming (or it might be pseudoprogramming) and is something which could be done; whether it's efficient is another question entirely.
I have a model which has various different types of information associated with it.
Opportunity.rb
has_many :notes
has_many :emails
has_many :calls
I currently have 3 tables on the show page where in the controller I call
#notes = #opportunity.notes.sorted
#emails = #opportunity.emails.sorted
#calls = #opportunity.calls.sorted
And display them in 3 tables
I'd like to sort them chronologically so I can get a timeline.
Each one of the models has date, and I want to sort by the date then return the objects in date order.
i.e.
The table goes:
Note | Content | 11-Feb | Bob
Call | Content | 07-Feb | Dave
Email | Content | 04-Feb | Steve
Call | Content | 03-Feb | Dave
Note | Content | 01-Feb | Margaret
I can't work out how I merge those tables in the controller into an object I can use in the view so that I can create a table, sort on the date ("created_at") and still keep everything looking nice and clean.
The simplest way would be to combine the 3 collections into an array and sort it by created_at. In your controller:
#combined = (#notes + #emails + #calls).sort_by {|record| record.created_at}
You can then use #combined in your view to show the timeline.
I am trying to create several instances of the same record on my rails app. I have tests and questions. tests can have many questions.
I also have testsessions. My testsessions table looks like this:
id | user_id | test_id | question_id | answer
I am trying to get the controller to create several instances of the same testsession, with the only thing differing being the question_id, which will be pulled from the questions that have the same test_id as the testsession.
At the moment I have this in my controller:
def new
#test = Test.find(params[:test_id])
#testsession = #test.testsessions.new
#testsession.user_id = current_user.id
#testsession.save
#questions = #test.questions
redirect_to action: :index
end
But I don't know how to make it create several instances based on the question_id.
I have an array of the question_id that I want to use:
#questions = Question.where(test_id: #test.id).pluck(:id)
But I don't know how to put this into the controller...in the end I would want the table to look something like this:
id | user_id | test_id | question_id | answer
1 | 1 | 1 | 1 |
1 | 1 | 1 | 2 |
1 | 1 | 1 | 3 |
1 | 1 | 1 | 4 |
Answer is always empty because this will be input by the user later using update
Any ideas on how to do this would be greatly appreciated!
I think I understand what you're trying to achieve: you have a test that has_many questions. When someone does the test, you want to create a test session for every question in advance.
Try this:
def new
#test = Test.find(params[:test_id])
#test.questions.each do |question|
#test.testsessions.create user: current_user, question: question
end
redirect_to action: :index
end
As a side note: the new controller action isn't really a good place to create things, because new is (normally) accessible through HTTP GET requests.
Assume for a moment that the app you write is accessible to Google. If Google finds a link to this controller action, it will visit it and accidentally create new test session objects. Possibly many times.
This is why controller actions that create or change something in your app should only be accessible through other methods, like a POST — search engines and other robots won't touch those.
Even if your app is not accessible in public, it's a good habit to get into.
Ok, suppose to have this db schema (relation):
|User | (1-->n) |Customer | (1-->n) |Car | (1-->n) |Support |
|--------| |---------| |-----| |-----------|
|id | | user_id | |Brand| |Description|
|username| |lastname | |PS | |Cost |
|password| |firstname| |seats| |hours |
|... | |.. | |... | |... |
The table User is generated by Authlogic.
I have 2 registred users, each one has his customers, etc. . With Authlogic I'm able to allow only authenticated users to reach controllers/views. That's fine, that's what Authlogic is made for.
Now I need to be sure that the user#1 will never reach informations belonging to customers of user#2.
In other words:
if the user#1 goes to http://myapp.com/cars he will see the list of cars belonging to customers of user#1
if the car with the id=131 belongs to the customer of user#1, only user#1 have to be able to reach this information (http://myapp.com/car/1). If the user#2 insert in the browser the same link he doesn't have to be able to see this information.
Some people suggested me to create a relation between the user and each db table in order to check if a record is associated to the current_user.
What do you think? What is the best approach/solution?
So you have 2 things:
In index page of cars controller only cars which belong to the current user should be shown.
You want to restrict show pages to the owner.
As for the index i suggest something like:
def index
# find logic
#cars = Car.find(:all,:conditions=>{:customer_id => current_user.customers.collect(&:id)})
# whatever else
# ...
end
And the show page:
def show
# .....
# after the find logic
redirect_to :index unless current_user.customers.collect(&:id).include? #car.customer_id
# whatever else
# ...
end
This approach is ok for most of the cases, however a better approach for performance is to add a user_id column to the costumers table, this is called denormalization but it's acceptable for performance wise.