Theres a lot of stuff around that is similar but nothing actually addresses this particular circumstance. I would like to preface by saying im still new in the Rails world.
So i have a User table and a Skill table and corresponding models.
I have created a join table to connect the 2 and have a HABTM relationship between the 2.
SKILL MODEL
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
end
USER MODEL
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
JOIN TABLE MIGRATION
class CreateJoinTableSkillsUsers < ActiveRecord::Migration
def change
create_join_table :skills, :users do |t|
t.index [:skill_id, :user_id]
t.index [:user_id, :skill_id]
end
end
end
ADDSKILL METHOD (In skills controller)
def addSkill
#user.id = current_user.id
#skill.id = Skill.find(params[:id])
params[:user_id, :skill_id]
redirect_to user_path
end
RELEVANT ROUTE?
put 'skills/addSkill' => 'skills#addSkill'
index.html.erb (skills)
<% commerceCategory.each do |skill| %>
<ul>
<%= button_to skill.title, :method => "addSkill" %>
</ul>
<% end %>
Submit Button
<%= submit_tag "Update Skills"%>
So basically i want:
a) skill.title to be a checkbox of itself (the name within the checkbox not one alongside it)
b)once its clicked, it will assign that particular skill to the current_user.id via the join table and redirect to the users show page to show the skills that user has.
I have tried so many different things and i cant seem to make it work.
Ive read all over that the has many through is a preferential association especially since i will be wanting to add varying degrees of skill for each skill eventually.
Any guidance would be greatly appreciated!
Related
I want the users to be able to write down their skills as hashtags in a text_field. I already can store a string and split it up like (btw: a user has one account)
<% #user.account.hashtag.split('#').reject { |c| c.empty? }.each do |d| %>
<p><%= d %></p>
<% end %>
But that is not elegant as it's processed in the view right now and since its just one string which is displayed as an array, I cant iterate. What I want to achieve is explained in this video.
A user should write down his skills in one field, the string should be split up at every '#' symbol and stored in a field which should belong to the user, so I can do something like url.com/user/hashtag/xyz while xyz is the hashtag.
The video tutorial is made well, but it does not work for rails 5+ since find_by is not available anymore; also I don't want to create some new tables, because later I want to do the same with other models than account. Later I want to add a autocomplete function in a search field via gems like select2. That is why it might help to add another table for the tags? :S
thanks in advance!
so there are lots of things in this short question.
The first thing that I would do would be to create a hashtag table
class CreateHashtags < ActiveRecord::Migration[5.0]
def change
create_table :hashtags do |t|
t.string :hashtag
t.references :hashtagsable, polymorphic: true
t.timestamps null: false
end
end
end
This line is critical
t.references :hashtagsable, polymorphic: true
This will create 2 new field
:hashtagsable_type => :string, # This reference the model of the assiciation
:hashtagsable_id => :integer, # This reference the id of the assiciation
This new model should look like
# app/models/hashtag.rb
class Hashtag < ApplicationRecord
belongs_to :hashtagsable, polymorphic: true
end
Now your user model you should add this line
class User < ApplicationRecord
has_many :hashtags, as: :hashtagsable # User.last.hashtags
end
class Account < ApplicationRecord
has_many :hashtags, as: :hashtagsable # Account.last.hashtags
end
in your view should look like
<% #user.account.hashtags.each do |hashtag| %>
<p><%= hashtags.hashtag %> </p>
<% end %>
I hope that this helps and set you in the right path
I have a model named 'Assessment':
class Assessment < ApplicationRecord
has_many :assessment_students
has_many :students, through: :assessment_students
end
Join table is:
class AssessmentStudent < ApplicationRecord
belongs_to :student
belongs_to :assessment
end
There is another model:
class Classroom < ApplicationRecord
has_many :classroom_students
has_many :students, through: :classroom_students
has_many :assessments
end
In show,html.erb of classrooms, I have a dropdown which shows all assessments (generated from assessment table).
Code is:
<%= collection_select(:assessment :assessment_id, Assessment.all, :id, :assessment_name , :prompt => true) %>
Requirement of the project is: Based on the assessment chosen by the user in the show.html.erb page, we have to show all students details like name etc assigned to that particular assessment. I have stored this data in join table 'AssessmentStudent '. However, I am not sure how to pass id from the above collection_select to classroom controller. I have below code:
show.html.erb:
<%= collection_select(:assessment :assessment_id, Assessment.all, :id, :assessment_name , :prompt => true) %>
<div id="divResult">
<% #assessmentstudents1.each do |t| %>
<% t.assessment_students.each do |record| %>
<%= record.student_id %>
<% end %>
<% end %>
</div>
classroom controller:
def show
#assessmentstudents1 = Assessment.find(params[:assessment][:assessment_id]).preload(:assessment_students)
end
def classroom_params
params.require(:classroom).permit(:classroom_name, :classroom_year, :customer_id, :classroom_student, :student_ids => [])
params.require(:assessment).permit(:assessment_id)
end
I would first recommend to make a change to the controller structure of your application. Because the responsibility of the show action of your ClassroomsController should be to display the details of the classroom. When you want to show the details of an Assessment, that should be handled by an AssessmentsController.
First of all, I'm gonna assume that your Assessment model has a belongs_to :classroom association. Then I would suggest creating the following structure.
config/routes.rb
resources :classrooms
resources :assessments
app/views/classrooms/show.html.erb
<% #classroom.assessments.each do |assessment| %>
<%= link_to assessment_name, assessment_path(assessment) %>
<% end %>
app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def show
#assessment = Assessment.find(:id)
end
end
app/views/assessments/show.html.erb
<h1>Students for <%= #assessment.assessment_name %></h1>
<% #assessment.students.each do |student| %>
<p><%= student.student_name %></p>
<% end %>
<h2>In classroom:</h2>
<p><%= #assessment.classroom.classroom_name %></p>
So to explain what is happening here, we have configured the routes to allow the server to respond to the url /assessments/:id which will lead to the AssessmentsController#show action being called.
That action is being called when the user clicks on any of the links that we have setup in the classrooms/show template. Note that I used individual links for now instead of a select dropdown, because it is easier to setup and understand how it works. And like the previous answer suggested, using a select tag requires a little bit of JavaScript to get working.
And lastly, when the assessments/show template is being rendered, it will list out all of the students related to that particular assignment. And I also included which classroom it is assigned to (if my assumption of belongs_to was correct).
On a side note, if your question tag of ruby-on-rails-3 is correct, then the usage of params.permit and params.require is invalid, because that is something that was introduced in Rails 4 (unless I'm mistaken). And in any case, you only have to use permit when database updates take place, which means the create and update actions, not index, show, etc, because it is a way of restricting which changes are allowed.
Tag select does nothing by itself. So there are two possible options.
The first is wrapping select in form tag then submitting the form will lead to request. The second is writing a handler using JavaScript which will listen to select changes.
I am fairly new to RoR and trying to get a basic app to work - I have a 'books' model and a 'genre' model. I wish to create a page that randomly generates books of different genre's for a user to select.
I have created a 'random_book' controller, but am unsure on how to proceed with the random selection and display.
Any help/pointers would be appreciated.
Edit:
Here's the work I've been doing in the random_book model:
" load 'user.rb'
class random_book < ActiveRecord::Base
belongs_to :book
belongs_to :genre
def get_random_book
find(:all).sample(5)
end
"
Thank you.
Based on discussion
4 models
Book
Genre
UserBook
User
They will look something like this:
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
class Genre < ActiveRecord::Base
has_many :books
def fetch_random_books(qty)
#you want to make sure you don't error out by requesting a sample of an empty list of books so check first. The qty argument lets you control the number of books for the search
unless self.books.empty?
self.books.limit(qty).sample
end
end
end
class Book < ActiveRecord::Base
belongs_to :genre
has_many :user_books
end
class UserBook < ActiveRecord::Base
belongs_to :book
end
I would most likely use a different route for the random book section, because it's not very url-friendly to say code-descriptive things like user_book.
There are 4 things to do
Create a new route to get a list of genre_ids that a user chooses.
Create an action that correlates to the route you created that renders a list of boos and adds those books to a users list
Create a form in a view (any view, like a sidebar in an existing books view, doesn't matter) this form will post the route and action you just made
Create a view to render the book list (the easy / DRY way is to add a few elements to the existing books index to let users know its a random generated list of books based on their genre pics)
Add the route
post "/random-books", to: "books#random_books", as: :random_books
Create the action in the books_controler
def random_books
if params[:genre_ids]
genres = Genre.where(id: params[:genre_ids])
#books = []
genres.each do |genre|
#books << genre.fetch_random_books(10)
end
else
#books = nil
end
respond_to do |format|
format.html { render action: :index }
end
end
Then create a form that makes a post request to the index action of the books_controller -- You can parse the form and update the UserBook model inside that action, and then display list of books all at the same time.
<%= form_tag(random_books_path, method: :post) do %>
<ul>
<% Genre.all.each do |genre| %>
<li>
<label class='genre-select'>
<%= check_box_tag 'genre_ids[]', genre.id -%>
<%= genre.name %>
</label>
</li>
<% end %>
</ul>
<%= submit_tag "Fetch Book List"%>
<% end %>
-- The last one I'm sure you can do, it returns a books object list so parse it however works best for you. In the controller action you can automatically add the ids for the books to the UserBook model by adding this inside the controller action:
#books.each{ |book| book.user_books.create(user: user)}
I have a database that contains users and groups with a has_and_belongs_to_many relationship. When a new group is added, it gets created but the user's membership to the group doesn't seem to propagate until I clear the cache or login with an incognito window. I know it's getting saved correctly, it just doesn't seem to be loading until the cache is cleared. This only recently started happening and I can't figure out why! Any help would be greatly appreciated.
From the models:
class User < ActiveRecord::Base
has_many :services
has_many :recipes
has_and_belongs_to_many :groups
attr_accessible :recipes, :groups
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :recipes
attr_accessible :description, :title, :recipe, :picture, :featured, :user_id
end
The create group method:
def create
#user = User.find(current_user.id)
#group = Group.new(params[:group])
#group.user_id = #user.id
#user.groups << #group
redirect_to group_path(#group)
end
Displaying the group memberships of a user -- this won't update until the cache is cleared:
<% #user.groups.each do |group| %>
<% if group %>
<p class="group-title"><a href="<%= group_path(group) %>"><%= group.title %></p>
<% #latestpic = Recipe.where("group_id = ?", group).limit(1).order("created_at DESC") %>
<% if #latestpic.exists? %>
<% #latestpic.each do |pic| %>
<%= image_tag(pic.picture.url(:medium)) %>
<% end %></a>
<% else %>
<%= image_tag "http://placehold.it/300x300" %>
<% end %>
<br></br>
<% end %>
<% end %>
In your models you have a "has and belongs to many" relationship, which means your users can be in n groups and your groups contains n users.
#group.user_id
If you have created a user_id column in your "groups" table, you can drop it, because a group contains n users. You have to use a table between users and groups like this :
create_table :group_users, :id => false do |t|
t.references :group, :null => false
t.references :user, :null => false
end
Then refactor your controller as I did below :
def create
#group = current_user.groups.build(params[:group])
if #group.save
redirect_to #group, notice: 'Group was successfully created.'
else
render action: "new"
end
end
This will create a group with your current user in it. In your method, you forgot to save your modifications. Because the operator = and << does not update the database. Then I refactored a little bit, but it is the same logic.
You can also refactor a lot of things in your view, but it's not the question, we'll leave it as is.
Does it work now?
May be this answer is outdated, but might be useful for googlers who ends-up here:
When Rails(4.2 for me) updates Has-And-Belongs-To-Many association, it does not change an updated_at value for the root record. Example:
# This does not change #user.updated_at value
#user.update_attributes(group_ids: [1, 2, 3])
Every ActiveRecord objects has a special cache_key that usually built using value of updated_at and invalidation of cache is based on that. So, if we change only HABT it does not invalidates cache.
Possible solution here - manually call #user.touch if HABTM was changed.
If someone came here because data won't show after create or delete, here is what you need to do to update the cache after those actions:
# in your model file
after_save :expire_model_all_cache
after_destroy :expire_model_all_cache
def expire_model_all_cache
Rails.cache.delete("your cache name")
end
I have a three-table system: Companies, Customers, and Matches. The matches include, for example, private notes that each party makes about the other and some options they can set regarding the other.
I have two sets of views centered around Companies and Customers for each party to look at the other and modify their notes and settings.
# Customers Controller
def show
#customer = Customer.find(params[:customer])
#matchings = #customer.matchings.find... #corrected
#company = Company.find(params[:company])
end
Obviously the #matchings is incomplete. Given that #matchings has fields customer_id and company_id, how do I find the right matching record? Thank you!
------UPDATE------
That was really helpful, thank you jdl!
The company asks the customer some questions and the customer responds. Questions are stored in Question and answers are stored in MatchingAnswer. To clarify the database's structure:
# models, aggregated
1st set of connections:
Company has_many :questionlists (QuestionList belongs_to :company)
QuestionList has_many :questions (Question belongs_to :question_list)
Question has_many :matching_answers (MatchingAnswer belongs_to :question)
2nd set of connections:
Company has_many :matchings (Matching belongs_to :company)
Matching has_many :matching_answers (MatchingAnswer belongs_to :matching)
Remember matching is the 'through' table between Company and Customer. I have to show to the company each question the company has asked and each answer to that question from a particular customer.
# QuestionLists controller
#questions = #questionlist.questions.find(:all)
#matchinganswers = #matching.matching_answers.find(:all, :conditions => ["question_id= ?", #question.id])
...except the condition needs to be satisfied by each question id supplied by my loop INSIDE the view. Let's take a look at the view.
# view
<% #questions.each do |q| %>
<li><%= q.question %></li>
<li><% q.matching_answers.each do |a| %>
<%= a.answer %>
<% end %></li>
<% end %>
I know this is riddled with holes.. I just haven't been able to fill them properly. How can I do the loop so underneath each question we see the answers the customer gave to that question? Thank you so much guys, this is an amazing group. :)
------UPDATE #2------
The problem is not the nesting, it is that q.matching_answers gives me EVERY answer that every customer has ever given to question q. I need to generate only those matching_answers that both stem from a particular question and match our matching_id (#matching.id). The MatchingAnswer model contains a matching_id field so each answer is unique to that specific match between customer and company. My problem is setting the right parameters so I get only those matching_answers that satisfy:
MatchingAnswer.matching_id = #matching.id
MatchingAnswer.question_id = #question.id
The problem is, while I DO have #matching.id calculated in the QuestionLists controller, I only have access to #question_list (there's only one) and #questions (all the questions belonging to that list). I thought I have to do a loop inside the view that says, for each question, give me those questions that satisfy MatchingAnswer.question_id = [this question.id]. I've been trying to do it with the above nest, but as you can tell, it doesn't give us the two parameters we need.
Reply to the original question about models.
Your models can do all of this for you, if you set them up correctly.
# company.rb
class Company < ActiveRecord::Base
has_many :matchings
has_many :customers, :through => :matchings
end
# customer.rb
class Customer < ActiveRecord::Base
has_many :matchings
has_many :companies, :through => :matchings
end
# matching.rb
class Matching < ActiveRecord::Base
belongs_to :company
belongs_to :customer
end
Then in your controller, if you have a #customer you can get either the matchings or the companies easily.
#customer = # however you figure out which customer you care about at the moment.
#companies = #customer.companies
#matchings = #customer.matchings
The reverse also works: i.e. #company.customers etc.
If you want to find all Matching objects for a given combination of customer and company, this will do it.
#matchings = Matching.find(:all, :conditions => ["company_id = ? AND customer_id = ?", #company.id, #customer.id])
I have no idea what a #candidate is in your question. I'm assuming that it's a typo.
If you want to clean up your controller a bit, you can push the find logic into a named_scope on Matching.
class Matching < ActiveRecord::Base
belongs_to :company
belongs_to :customer
named_scope :for_company_and_customer, lambda {|comp, cust| {:conditions => {:company_id => comp, :customer_id => cust}}}
end
With this, your code to find matches in the controller once you've found the company and customer would be like this.
#matchings = Matching.for_company_and_customer(#company, #customer)
Reply to the updated question about the views.
I'm assuming that your models are fine now, and you can get the data you need. You should be able to simply nest your HTML lists to get what you're looking for. Obviously, there are fancier ways to do this.
<% #questions.each do |q| %>
<li><%= h q.question %>
<ul>
<% q.matching_answers.each do |a| %>
<li><%= h a.answer %></li>
<% end %>
</ul>
</li>
<% end %>
(Note the use of the h method. You want to escape user-supplied data to avoid potential security problems.)