i got 2 tables connected with each other.
device and push information are my models.
class Device < ActiveRecord::Base
has_one :pushinformation
end
class Pushinformation < ActiveRecord::Base
belongs_to :device
end
these are my 2 model files and their relationships.
and these are my migration files
class CreateDevices < ActiveRecord::Migration
def change
create_table :devices do |t|
#t.integer :id
t.string "token"
t.string "carrier"
t.string "segment"
#t.datetime :created_at
#t.datetime :updated_at
t.timestamps
end
end
end
class CreatePushinformations < ActiveRecord::Migration
def change
create_table :pushinformations do |t|
t.integer "device_id"
#t.string "token"
t.string "first_name"
t.string "last_name"
t.string "nickname"
t.timestamps
end
end
end
now the thing is , i was able to create a relationship successfully in rails console by saying
device.pushinformation=push
and they were associated.
How can i make this process automated, like when i add one device- it will have a push information table filled aswell,
i thought about having the same attribute and relating them might be the solution. In this case its token and its completely unique.
How can i create this relationships? I need to be able to know which device has what kind of first_name
i m a beginner in ruby and this is a newbie question sorry guys :)
I am not sure I understand completely what you ask but my guess is that you could use a callback on create
class Pushinformation < ActiveRecord::Base
belongs_to :device
after_create :create_push_notification
private
def create_push_notification
...
end
end
check the docs
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
xlembouras's answer is right (to a degree), but as you're new, let me explain it for you:
--
Associations
ActiveRecord associations are nothing magical, they're just a way to associate two "objects" using a relational database setup.
ActiveRecord is an ORM -- "object relationship mapper" -- which basically means it just provides a level of abstraction for your ActiveRecord objects to associate with each other
Herein lies the crux of the matter - you need to apprciate how and why your associations will work, and more importantly, how to populate them correctly:
--
Models
Firstly, you need to appreciate the object-orientated nature of Ruby (& by virtue of running on Ruby, Rails). This is where the Models of Rails play such a vital role -- they allow you to build & manage objects from your database
The ActiveRecord associations give you the ability to manage the associations those objects have - maning if you build one, you should be able to build the other:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
before_create :build_push_information #-> creates associative object before creation
end
#app/models/push_information.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
You need to consider the importance of what I've written above.
What you need is to create a push_information object with the same foreign_key as the device object, which can be achieved by using the build method
This will essentially create a "blank" version of your associative object, saving it with the correct foreign key etc
--
Nesting
Further to this, you have to appreciate the idea of "nesting", especially the method accepts_nested_attributes_for
This allows you to create associative / dependent objects based on your "parent" object:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
accepts_nested_attributes_for :push_information
end
#app/models/push_informtion.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
This gives you the ability to do the following:
#app/controllers/devices_controller.rb
Class DevicesController < ApplicationController
def new
#device = Device.new
#device.build_push_information
end
def create
#device = Device.new(device_params)
#device.save
end
private
def device_params
params.require(:device).permit(:your, :device, :params, push_information_attributes: [:push, :information, :attributes])
end
end
This gives you the ability to populate the devices#new form like so:
#app/views/devices/new.html.erb
<%= form_for #device do |f| %>
<%= f.text_field :your_device_attributes %>
<%= f.fields_for :push_information do |p| %>
<%= p.text_field :your_field %>
<% end %>
<%= f.submit %>
<% end %>
Add a create
method to your Devise class. Something like:
def self.create(token, carrier, segment)
device = Device.new(:token => token, :carrier => carrier, :segment => segment)
pi = Pushinformation.create(device.id,..) # other params
end
Related
I want to make a table called subject_types and populate it with different subjects such as english, spanish, history. And assign the subjects table to each user. I want the user to be able to select multiple subjects with a checkbox and it will save those specific ones and keep checked. each user can save their own preferences/services. Im trying to figure out the best way to approach this.
here is how it would look
in a way the user is editing their settings. I want to be able to take this information and use it later to find results with only those subject types that they select. is there a tutorial or similar reference to this?
-Should I make the subject_types model and populate with these subjects or is there a better approach?
possible solution
could I have an array in a column in my user table? the array would be the ids of the subjects table. How can i do that??
Thank you
Updated:
<%= form_for(#user) do |f| %>
<%= f.collection_check_boxes(:writing_type_ids, WritingType.all, :id, :name) %>
<%= f.submit 'submit' %>
<% end %>
class WritingType < ActiveRecord::Base
has_many :user_writing_types
has_many :users, through: :user_writing_types
end
class UserWritingType < ApplicationRecord
belongs_to :user
belongs_to :writing_type
end
class User < ActiveRecord::Base
has_many :user_writing_types
has_many :writing_types, through: :user_writing_types
end
My migration for the join
create_table :user_writing_types do |t|
t.belongs_to :user, index: true
t.belongs_to :writing_type, index: true
t.boolean :active, default: false
t.timestamps
end
latest update
Im getting my last of errors! when i click submit on that page i get No route matches [PATCH] "/users/51".
I added this <%= f.collection_check_boxes(:writing_type_ids, WritingType.all, :id, :name) %> to my edit form in devise edit.html.erb. The writing_type names populate on the checkboxes, but nothing gets submitted to the database on the user_writing_type table.
Start by creating a many to many relation with a join model by using has_many :through associations:
class User < ApplicationRecord
has_many :user_subjects
has_many :subjects, through: :user_subjects
end
class Subject < ApplicationRecord
has_many :user_subjects
has_many :users, through: :user_subjects
end
# this is the join model
class UserSubject < ApplicationRecord
belongs_to :user
belongs_to :subject
end
You can then create a check box to add subjects to a user with collection_checkboxes:
<%= form_for(#user) do |f| %>
<%= f.collection_checkboxes(:subject_ids, Subject.all, :id, :name) %>
# ...
<% end %>
could I have an array in a column in my user table? the array would be
the ids of the subjects table. How can i do that??
You don't.
Even though Postgres for example allows you to create array type columns
it is not a good solution as thats not how associations work in ActiveRecord.
Its also a crappy relational database design as its messy to write queries with a join though an array column and it does not let you enforce referential integrity with foreign keys or have good indices for performance.
I know similar questions have been asked for this subject and I have read all of them and was not able to figure out a clear solution. Before I state my problem, I will post all the required code.
The models:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :scoreboard
end
class User < ActiveRecord::Base
has_many :scoreboards, dependent: :destroy
has_many :comments, dependent: :destroy
end
class Scoreboard < ActiveRecord::Base
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
The scoreboard is similar to a article page where users can post comments.
The migration for the Comments:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :body
t.text :reply
t.references :user, index: true
t.references :scoreboard, index: true
t.timestamps null: false
end
add_foreign_key :comments, :users
add_foreign_key :comments, :scoreboards
end
end
The problem is with the create method in the comments controller. Here is the code for the method:
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment.user_id = current_user.id
#comment = #scoreboard.comments.build(comment_params)
redirect_to scoreboard_url(#comment.scoreboard_id)
end
The current_user method is located in a helper file in a separate folder.
Whenever I submit the form for a new comment, I get the following error:
undefined method `user_id=' for nil:NilClass
One of the questions on stack stated that a user_id column is needed in comments, and when I tried migrating it said duplicated column cannot be created. Could it because there is a foreign key to users already present in the migration? What could I be doing wrong?
The error is quite simple:
#comment.user_id = current_user.id
#comment = #scoreboard.comments.build(comment_params)
You're calling #comment without having defined it previously.
It should be like this:
#comment = #scoreboard.comments.build comment_params
#comment.user_id = current_user.id
One of the questions on stack stated that a user_id column is needed in comments
To clarify, they were referring to the foreign_key of the Comment model.
You must remember that Rails is built on top of a relational database:
Whichever variant of SQL you use, you'll still be using it in a relational manner; to which Rails has added the ActiveRecord "object relational mapper".
Simply, this gives Rails the ability to call associative data with different queries etc. We don't see any of the technicalities; only the #scoreboard.comments association.
In the backend, Rails has to compute which data is related to the other. This can only happen when using appropriate relational database structures - including the use of foreign_keys.
This is why you have to assign the likes of user_id when creating associated objects. There is a trick to make it more succinct, though:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#scoreboard = Scoreboard.find params[:scoreboard_id]
#comment = #scoreboard.comments.build comment_params
end
private
def comment_params
params.require(:comment).permit(:params).merge(user_id: current_user.id)
end
end
I'm used to MySQL but trying to use Ruby on Rails right now. In MySQL, I would have two tables, with one containing a reference to another ("posts" referring to "topic"). A MySQL query doing what I want would be similar to "SELECT * FROM Posts WHERE posts.topic="topic" ("topic" here is a variable).
However, trying to work with the Ruby model stuff has me confused. The variables being passed between the controller and view are null because they are empty tables.
In my controller:
def topic
#topic = Topic.where(params[:topic])
#posts = Post.where(topic: #topic.object_id)
end
I don't know how to select the posts which have the topic defined by the "topic" variable.
In the view:
<% #posts.each do |post| %>
<p><%= post.title %></p>
<% end %>
The migration files:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.string :text
t.references :topic
t.timestamps
end
end
end
class CreateTopics < ActiveRecord::Migration
def change
create_table :topics do |t|
t.string :topic
t.timestamps
end
end
end
Given that Post and Topic are related, according to your migrations at least, in the models you should be stating"
class Topic
has_many :posts
and
class Post
belongs_to :topic
Given that you then have an instance of Topic, #topic, you can retrieve all the related records with:
#posts = #topic.posts
I think those methods you put in your controller are fine where they are, but keep in mind that the Rails way is "fat models, skinny controllers." If you put that logic in the model as a method, it's much easier to read in the controller. Also, you should look into scopes, as they'll help you with queries like this down the line too.
In any case, you should stick the following in your Topic model:
scope :by_name, ->(name) { where(topic: name) }
That's essentially the same as doing the following:
def self.by_name(name)
where(topic: name)
end
On your posts model, you'd be able to do the following:
scope :by_topic, ->(topic) { where(topic_id: topic) }
The other problem with what you've stuck in your controller is that when you use scopes, or a "where", it returns an array that contains all of the different records that match your query terms. So, when you call #topic = Topic.where(params[:topic]), you're getting back an array of objects. Therefore, when you do a #topic.id, you're trying to get back the id of an array instead of one object.
Based off of what I showed you before, it makes much more sense for you to do this:
def topic
#topic = Topic.by_name(params[:topic]).first #this returns the first record
#post = Post.by_topic(#topic.id)
end
That will return an array of posts that match the first topic name that you query for.
Alright, first a primer on how database design and how Rails (really, ActiveRecord) works. Basically, you should be connecting posts.topic_id = topic.id, not posts.topic = topic.topic.
Your migration is correct as is, create_table automatically includes an :id PRIMARY KEY column. That said you should know that these are all equivalent:
t.references :topic
t.belongs_to :topic
t.integer :topic_id
In your view, instead of embedding topic.topic and passing that to the controller when the form is submitted, embed topic.id (the documentation for the select helper has a good example of this) and in your controller:
#topic = Topic.find params[:id]
#posts = #topic.posts
I'm trying to create a form using form_for which will add Employees. For an employee i want to assign multiple specializations such as c#, asp, etc as the values dynamically. I'm using the following code in the form
<%= f.select :specilization, Specialization.all.collect{|p| [p.name, p.id]}, {}, :multiple => true %>
I've also made HABTM between employees and specialization like
Employee.rb
class Employee < ActiveRecord::Base
has_and_belongs_to_many :specializations
end
Specialization.rb
class Specialization < ActiveRecord::Base
has_and_belongs_to_many :employees
end
with these done, im not able to save the selected values in db(MySQl). Would appreciate if anyone could solve my problem or guide me on how to get this right?
Thanks in advance.
I usually solve this by using has_many :through and then in my form the select is a field_for on the join model. Like this:
class Employee < ActiveRecord::Base
has_many :employees_specializations
has_many :specializations, through: :employees_specializations
# we will be creating these join models on the employee form
accepts_nested_attributes_for :employees_specializations
end
class Specialization < ActiveRecord::Base
has_many :employees_specializations
has_many :employees, through: :employees_specializations
end
class EmployeesSpecialization < ActiveRecord::Base
belongs_to :employee
belongs_to :specializations
end
Now it's important to note that with this simplified approach I'm assuming Specializations already exist in the database and we're just selecting them and joining them to the employee we're creating/editing:
# in your controller make sure to build at least 1 new instance, the fields_for needs this
#employee.employees_specializations.build
# remember to add to your strong parameters the new join attributes
params.require(:employee).permit(
# ... other attributes ...
employees_specializations_attributes: [:id, :specialization_id]
)
You need to declare :id, :specialization_id as sub fields since employees_specializations_attributes will be a nested hash with those keys inside.
# now in your form use fields_for
<%= f.fields_for :employees_specializations do |ef| %>
<%= ef.select :specialization_id, Specialization.all.map{|p| [p.name, p.id]}, {}, multiple: true %>
<% end %>
That f.fields_for :employees_specializations will create form fields named employee[employees_specializations_attributes][][specialization_id]. Which basically says we are creating a nested association employees_specializations and setting that nested association's specialization_id (remember employees_specialization belongs_to :specialization) to the selected specialization. Note the [] in the middle of the field name, this means its an array of nested employees_specializations.
Post that and barring any validation errors you should be able to create/edit an employee and set it's specializations by selecting from a list of existing specializations and creating a join model between them.
Further reading:
http://apidock.com/rails/v4.0.2/ActionView/Helpers/FormHelper/fields_for
http://apidock.com/rails/v4.0.2/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for
http://laptite.github.io/blog/2014/02/26/deep-nesting-with-has-many-through-and-a-join-model/
Rails nested form with has_many :through, how to edit attributes of join model?
I'm working on a quiz app in Rails that keeps track of a number of Facts
app/models/fact.rb
class Fact < ActiveRecord::Base
validates(:question, presence: true, uniqueness: { case_sensitive: false })
validates(:answer, presence: true)
end
Every time a user takes a new quiz, they generate an Exam
app/models/exam.rb
class Exam < ActiveRecord::Base
after_create :assign_facts
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :user_id, presence: true
has_many :problems
has_many :facts, through: :problems
def assigned?(fact)
problems.find_by(fact_id: fact.id)
end
def assign!(fact)
problems.create!(fact_id: fact.id)
end
private
def assign_facts
facts = Fact.all.sample(10)
facts.each do |fact|
self.assign!(fact)
end
end
end
Since there are many Exams all using the same Facts, each Exam has_many Facts though an association table of Problems
app/models/problem.rb:
class Problem < ActiveRecord::Base
belongs_to :exam
belongs_to :fact
validates :exam_id, presence: true
validates :fact_id, presence: true
end
excerpt from db/scheme.rb:
create_table "problems", force: true do |t|
t.integer "exam_id"
t.integer "fact_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "correct", default: false
end
My problem is that I'm trying to figure out how to store the results of each user's exam (whether they answer a specific question either correctly, or incorrectly). I was planning on updating the association table each time the user answers a question and storing the result in the t.boolean "correct" collumn. This would be a fairly simple matter in PHP/MySQL (UPDATE problems SET correct = 1 WHERE exam = 'id' AND fact = 'id'), but I'm having difficulty figuring out how to do it the Rails way.
Is there some way I can simply, and easily update my associations table (problems) with Rails? OR Should I create a fourth table (maybe 'results' or something) to keep track of the user's correct/incorrect answers? --I know I don't have any controller code here, I'm just thinking out the broad strokes, and I want to keep things simple. Any help would be greatly appreciated.
You are almost there... you have already added a Boolean column called correct in the schema for the problem model, so now you just need to access that as an attribute when updating an Exam. Som somewhere in your controller code, you would say:
ps=#exam_taken.problems
ps.each do |p|
if answered_correctly(p)
p.correct=true
p.save
end
end
# This assumes you have a method that checks correctness inside the binding where the
# above code is written
#exam_taken.save
If you are using Rails3, you would have to also declare the correct attribute as attr_accessible in your model.
And here's a free pro-tip: Default scope is evil :)