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
Related
I have a collection of objects. What I would like to do is iterate over the entire collection, but show each object on a page/view by itself, and allow the user to interact with each object individually. Ideally, I would prefer not to use a multi-part form if I can avoid it, for reasons I spell out at the end of my question.
I am trying to implement screens like the images below.
Basically the user of the app, will go to a location to do a reconciliation of inventory (of each product) in that location. That's what the screens are showing. For each product, they have to update the inventory.
The summary & requirements are as follows:
A location has_many inventory_items.
A user begins a reconciliation whenever they want to do an inventory check.
A reconciliation habtm inventory_items && belongs_to :location.
An inventory_item habtm reconciliations && belongs_to :location.
I can't predict in advance how many inventory_items there are.
There could be dozens or hundreds of inventory_items.
I can break up the inventory_items into different groups if the number becomes unwieldy....similar to a pagination approach.
So my models look like this:
Reconciliation
# == Schema Information
#
# Table name: reconciliations
#
# id :integer not null, primary key
# location_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Reconciliation < ApplicationRecord
belongs_to :location
has_and_belongs_to_many :inventory_items
end
Location
# == Schema Information
#
# Table name: locations
#
# id :integer not null, primary key
# name :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Location < ApplicationRecord
has_and_belongs_to_many :products
has_many :inventory_items, inverse_of: :location
accepts_nested_attributes_for :inventory_items
has_many :reconciliations
end
Inventory Item
# == Schema Information
#
# Table name: inventory_items
#
# id :integer not null, primary key
# product_id :integer
# location_id :integer
# quantity_left :integer
# quantity_delivered :integer
# quantity_on_hand :integer
# date_of_last_delivery :datetime
# created_at :datetime not null
# updated_at :datetime not null
#
class InventoryItem < ApplicationRecord
belongs_to :product
belongs_to :location, inverse_of: :inventory_items
has_and_belongs_to_many :reconciliations
end
Here is my inventory_items_reconciliations Join Table:
create_table "inventory_items_reconciliations", id: false, force: :cascade do |t|
t.bigint "inventory_item_id", null: false
t.bigint "reconciliation_id", null: false
t.index ["inventory_item_id", "reconciliation_id"], name: "index_inventory_item_id_reconciliation_id_join"
t.index ["reconciliation_id", "inventory_item_id"], name: "index_reconciliation_id_inventory_item_id_join"
end
My routes.rb:
resources :locations, shallow: true do
resources :inventory_items
resources :reconciliations
end
My ReconciliationsController#New:
def new
#location = Location.find(params[:location_id])
#reconciliation = #location.reconciliations.new
#inventory_items = #location.inventory_items
#num_of_inventory_items = #inventory_items.coun
end
My app/views/reconciliations/new.html.erb:
<% #inventory_items.each do |inventory_item| %>
<%= render 'form', reconciliation: #reconciliation, inventory_item: inventory_item %>
<% end %>
My app/views/reconciliations/_form.html.erb:
<%= simple_form_for #reconciliation, url: :location_reconciliations do |f| %>
<%= f.error_notification %>
<strong>Name</strong>: <%= inventory_item.product.name %> <br />
<strong>Quantity Left:</strong> <%= inventory_item.quantity_left %> <br />
<strong>Quantity Delivered:</strong> <%= inventory_item.quantity_delivered %> <br />
<div class="form-actions">
<%= f.button :submit, "Update", class: "btn btn-primary" %>
</div>
<% end %>
What this does is just displays all of the location.inventory_items on that reconciliation page, when all I want is for 1 to be displayed.
So, what I would like to do is this:
Get the collection of inventory_items in the location that the user has chosen.
Begin iteration over that collection and show the user each object, in their own view, one at a time.
As the user progresses (i.e. once they press 'Next'), essentially mark that inventory_item as reconciled even if the user didn't update the quantity (i.e. say there have been no sold since it was delivered).
Once all inventory_items in this collection are iterated over, then save the reconciliation record to the database that accurately reflects the quantity information for each inventory_item within this reconiliation cycle.
I looked at the Wicked Gem, but it seems that I need to be able to statically declare the number of steps in advance. As you can see above, if each inventory_item in my collection is a step, there needs to be a dynamic number of steps.
I also came across similar constraints with other multi-step wizard approaches.
How do I achieve what I am trying to do?
What is a reconciliation?
i) “A procedure for confirming that the balance in a chequebook matches the corresponding bank statement. This is normally done by preparing a bank reconciliation statement.
ii) A procedure for confirming the reliability of a company’s accounting records by regularly comparing [balances of transactions]. An account reconciliation may be prepared on a daily, monthly, or annual basis.”
user has_many items
warehouse has_many accounts
accounts has_many items
warehouse has_many items, throught: :accounts
item belongs_to warehouse
class Product
has_many :items
accepts_nested_attributes_for :items
end
class Item
belongs_to :product
end
class Account
has_many :products
has_many :warehouses, through: :products
end
class Warehouse
has_many :products
has_many :accounts, through: :products
end
Your routes.rb
resources :warehouses do
resources :products
resources :accounts
end
resources :accounts do
resources :products
resources :warehouses
end
resources :products do
resources :items
end
The account model will have a reconciled field.
This is your form. It is displaying a Product.
form_for #product do |f|
<!-- your fields: account_id and number of items-->
f.fields_for :items do |item|
<!-- your item -->
end
f.submit
end
ProductsController#update
def update
warehouse = Warehouse.find(params[:warehouse_id])
#product = warehouse.products.first
# needs to be refactored
if #product.update && #product.is_the_last_one
redirect_to :your_choosen_root
elsif #product.update
redirect_to edit_product_path(#product.next)
else
render :edit
end
end
Once all inventory_items in this collection are iterated over, then save the reconciliation record to the database that accurately reflects the quantity information for each inventory_item within this reconiliation cycle.
I'll think about it tomorrow (it's 22:55) :)
Like Simple Lime said...I skimmed. That said, I agree with rethinking having standard pagination and save on click. I'd be pretty peeved if my thumb hit something and then had to start over.
Anyway, I would set the display of each content block to display="none" and wait wait for selectionOnChange or onFormSubmit or whatever the previous block is waiting for - if I understand your use case correctly.
My approach. Start an empty reconciliation (new and create methods). This creates a reconciliation with all inventory items copied from the items in the location. After creating the reconciliation, you are redirected to the edit form, which only edits one item. The update method redirects again to the update, but for the next item... There can be many errors as I don´t know all your models and have not tested (has wrote here directly). But the idea should work. Just adapt to your needs.
ReconciliationsController
def new
#location = Location.find(params[:location_id])
# Start reconciliation (without items)
#reconciliation = #location.reconciliations.new
end
def create
#location = Location.find(params[:location_id])
# Create an empty reconciliation (params do not include items)
#reconciliation = #location.reconciliations.new(reconciliation_params)
#inventory_items = #location.inventory_items
# Add items from location (this may require some changes depending on how you defined your models
# If it does not work, you can create all inventory items in a loop
# for each #inventory_items
#reconciliation.inventory_items = #inventory_items
if #reconciliation.save
#Start editing reconciliation, from first item
redirect_to edit_reconciliation_path(#reconciliation, item: 0)
else
redirect_to 'new'
end
end
def edit
#reconciliation = Reconciliation.find(params[:id])
#inventory_items = #reconciliation.inventory_items.order(:id)
#inventory_item = #inventory_items[params[:item]]
#item = params[:item].to_i + 1
#item = -1 if #inventory_items[#item].nil? # End condition.
end
def update
#reconciliation = Reconciliation.find(params[:id])
#reconciliation.update_attributes(reconciliation_params)
if params[:item] != -1
redirect_to edit_reconciliation_path(#reconciliation, item: item)
else
redirect_to root_path
end
end
views/reconciliations/new
<%= simple_form_for #reconciliation do |f| %>
.... fields for the reconciliation.
.... Don't include the form for inventory_items
.... So that you create an empty reconciliation
<%= hidden_field :location_id %>
<%= f.submit "Start reconciliation %>
<% end %>
views/reconciliations/edit
<%= simple_form_for #reconciliation do |f| %>
.... fields for the reconciliation.
.... Include the form for only 1 inventory_item
<%= f.simple_fields_for #inventory_item do |ff| %>
<%= #inventory_item.product.name %> <br />
</strong> <%= #inventory_item.quantity_left %> <br />
</strong> <%= #inventory_item.quantity_delivered %> <br />
... Fields to change data
<%= hidden_field :item %>
<% end %>
<%= f.submit (#item == -1 ? "Finish" : "Next") %>
<% end %>
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!
I have a student profile page. There's a form on that page which allows you to create new Note record with for that student. Works well.
I want to add a new field above the "Note" text field labeled "Who?" which allows you to enter additional students, thereby logging notes in bulk:
I'm not quite sure how to structure my form and controller action to achieve this bulk creation of new Note records associated with each student listed.
I'm thinking of taking this approach:
POST to the same action (/notes#create) and detect the presence of the array field student_ids in the params and then do something like this:
params[:student_ids].each do |s|
note_params[:event_attributes][:student_id] = s
note = Note.new(note_params)
note.save
end
But I'd have to modify note_params so that it contains the proper student_id reference on each iteration.
Currently, on the singular form, note_params looks like this:
=> {
"content" => "test",
"event_attributes" => {
"student_id" => "2",
"user_id" => "1",
"is_milestone" => "0",
"happened_at_string" => ""
}
}
Is there a better/cleaner/easier way to do this without having to loop through each id and manually modify the params?
You don't need to modify params that way.
params.fetch(:student_ids, []).each do |student_id|
student = Student.find(student_id)
note = student.notes.new(note_params)
note.save
end
I think you'd be better creating some sort of join table for this functionality.
This way, you can create one note, and then "copy" it by simply duplicating the join records. The downside is that one note may be accessible to multiple people, but that's okay:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :created_notes, class_name: "Note", foreign_key: :user_id
has_and_belongs_to_many :notes
end
#app/models/note.rb
class Note < ActiveRecord::Base
belongs_to :user #-> to know who made it
has_and_belongs_to :users
end
Then you can use your notes#create method as follows:
#app/controllers/notes_controller.rb
class NotesController < ApplicationController
def new
#note = current_user.created_notes.new
#users = User.where.not(id: current_user.id)
end
def create
#note = current_user.created_notes.new note_params
#note.save
end
private
def note_params
params.require(:note).permit(:body, :user_ids)
end
end
Thus, you'll be able to use the collection_select (or similar) functionality to define user_ids for your note:
#app/views/notes/new.html.erb
<%= form_for #note do |f| %>
<%= f.collection_select :user_ids, #users, :id, :name %>
<%= f.submit %>
<% end %>
I want to display login student name and message.
after login student can write messages and send related to courses.the messages he send is displayed above in same page with his/her name and message
I got name, but message field fetches all messages that are in database. How to display a particular student name and message?
Here is my code
controller.erb
class CourseQueriesController <ApplicationController
def index
#course_queries = CourseQuery.all
#course_query = CourseQuery.new
end
def create
#course_query = CourseQuery.new(student_id: current_student.id, coach_id: "2", message: params[:course_query][:message])
if #course_query.save
redirect_to course_queries_path, notice: 'Query was successfully send.'
else
render :new
end
end
end
course_queries/index.html.erb
<% #course_queries.each do |queries| %>
<p><b><%= current_student.name %></b></p>
<%= queries.message %>
<% end %>
<%= simple_form_for (#course_query) do |f| %>
<%= f.input :message %>
<%= f.button :submit , "Send or press enter"%>
<% end %>
how to display a particular student name and message
You need to have the relevant associations established in your models, like what Pavan wrote.
I'll give you some more information on why this is important...
ActiveRecord
One of the main reasons Rails works so well is the way it helps you create & manage objects. In OOP, objects form everything from your init commands to your user input responses, Ruby being a prime exponent of this structure.
Rails is built on Ruby, and therefore is object orientated too. It uses ActiveRecord, the MVC structure & classes to give you a platform from which you can populate and manipulate objects:
Thus, you shouldn't be treating your application's interactions as a way to edit a database, or "display a login message" - it should be a way to invoke & manipulate objects.
Objects - in the case of Rails - are built in the models. The model data can then be used in the controllers and views.
This seems to be lacking in your code. If you can remedy it, your code will become a lot simpler and more powerful...
Associations
I'd do something like this:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :queries
has_many :coarse_queries, through: :queries
end
#app/models/course.rb
class Course < ActiveRecord::Base
has_and_belongs_to_many :coaches
has_many :queries
has_many :student_queries, through: :queries
end
#app/models/coach.rb
class Coach < ActiveRecord::Base
has_and_belongs_to_many :courses
has_many :queries
end
#app/models/query.rb
class Message < ActiveRecord::Base
belongs_to :course
belongs_to :student
belongs_to :coach (maybe)
end
This structure will allow a student to send queries to specific courses, selecting the coach as necessary. Importantly, this sets up your associations so that you don't have to invoke multiple classes each time you want to populate the various objects.
#app/controllers/course_queries_controller.rb
class CourseQueriesController <ApplicationController
def index
#queries = Query.all
#query = current_student.queries.new
end
def create
#query = current_student.queries.new query_params
if #query.save
redirect_to course_queries_path, notice: 'Query was successfully send.'
else
render :new
end
end
private
def query_params
params.require(:query).permit(:message).merge(coach_id: "2")
end
end
#app/views/queries/index.html.erb
<% #queries.each do |query| %>
<p><b><%= query.student.name %></b></p>
<%= query.message %>
<% end %>
<%= simple_form_for #query do |f| %>
<%= f.input :message %>
<%= f.button :submit , "Send or press enter"%>
<% end %>
You should add has_many :course_queries to the Student model
#student.rb
class Student < ActiveRecord::Base
has_many :course_queries
...
end
And in the controller in index method change #course_queries = CourseQuery.all to #course_queries = current_student.course_queries
Now <%= queries.message %> will only display the course_query's message of the current_student
I'm trying to build a form for an "Incident" that allows users to add notes (text fields) to it dynamically with some javascript. So when they click an "Add Note" button in the form, a text field pops up and they can add a note. If they click it again, another field will show up. So far this works fine when creating a new incident, but when I edit the incident and add a new field, it doesn't pick up the relationship between incident_note and user.
For example, here is what I see when I create a new incident.
INSERT INTO "incident_notes" ("created_at", "updated_at", "user_id", "note", "incident_id") VALUES('2010-07-02 14:07:42', '2010-07-02 14:07:42', 2, 'A Note', 8)
As you can see, the user_id field has a number assigned to it. But here is what happens during the edit when I add another note:
INSERT INTO "incident_notes" ("created_at", "updated_at", "user_id", "note", "incident_id") VALUES('2010-07-02 14:09:11', '2010-07-02 14:09:11', NULL, 'Another note', 8)
The user_id is NULL. I'm not sure what I've done. The code is very similar for both "edit" and "new" in the controller.
I have the following models and relationships (only showing the relevant sections):
class Incident < ActiveRecord::Base
has_many :incident_notes
belongs_to :user
end
class IncidentNote < ActiveRecord::Base
belongs_to :incident
belongs_to :user
end
class User < ActiveRecord::Base
has_many :incidents
has_many :incident_notes
end
Here is the relevant part of the new form (the edit is essentially the same):
<% form_for([#customer,#incident]) do |f| %>
<p>
<% f.fields_for :incident_notes do |inf| %>
<%= render "incident_note_fields", :f => inf %>
<% end %>
<p><%= link_to_add_fields "Add Note", f, :incident_notes %></p>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
And here are the create and update methods in the Incident controller.
def create
#incident = #customer.incidents.build(params[:incident])
#incident.capc_id = generate_capc_id
for inote in #incident.incident_notes
(inote.user = current_user) if (inote.user == nil)
end
respond_to do |format|
if #incident.save #etc
end
def update
#incident = #customer.incidents.find(params[:id])
for inote in #incident.incident_notes
(inote.user = current_user) if (inote.user == nil)
end
respond_to do |format|
if #incident.update_attributes(params[:incident])
#etc
end
There may be a better way to do this, but as you can see in the "create" method I had to manually set the incident_note user field to the current user. This works fine, but the same does not seem to work in the update method.
Any ideas, suggestions, and help will be much appeciated! I'm very stuck at the moment. :)
I suggest that you don't have incident_notes directly belonging to user. In other words a user has many incidents and an incident has many incident notes.
class Incident < ActiveRecord::Base
has_many :incident_notes
belongs_to :user
end
class IncidentNote < ActiveRecord::Base
belongs_to :incident
end
class User < ActiveRecord::Base
has_many :incidents
has_many :incident_notes, :through => :incident
end
The user's incident notes are then obtained through her incidents model