I have the following schema:
attribute
----------
id
name
profile
----------
user_id
attribute_id
value
user
----------
id
name
What I am trying to do is display all the attributes and then for any attribute that the user does have in the profile it fills it in and an update can be performed. My background isn't ruby but I'm testing this framework for a proof of concept before possibly migrating an app.
I have mapped the association like that on the guides on rubyonrails.org (the appointments example)
class Attribute < ActiveRecord::Base
has_many :profiles
has_many :users, through: :profiles
end
class Profile < ActiveRecord::Base
belongs_to :attribute
belongs_to :user
end
class User < ActiveRecord::Base
has_many :profiles
has_many :attributes, through: :profiles
accepts_nested_attributes_for :profiles
end
I took the approach of using nested forms but unable to pass the model through even though the accepts_nested_attributes_for has been set.
<%= form_for(#user) do |f| %>
<% Attribute.all.each do |attribute| %>
<div>
<%= attribute.name %>
</div>
<div>
<%= f.fields_for :profiles do |upp| %>
<%= upp.hidden_field :user_id, :value => #user.id %>
<%= upp.hidden_field :attribute_id, :value => #attribute.id %>
<%= upp.text_field :value %>
<% end %>
</div>
<% end %>
<div>
<%= f.submit %>
</div>
<% end %>
e.g
Attributes
A
B
C
User has attribute A
A ["hello world"]
B [ ]
C [ ]
Attributes are dynamic, so if 2 new attributes are added, D and E would be shown as well
A ["hello world"]
B [ ]
C [ ]
D [ ]
E [ ]
How can I setup this form correctly so that it can be passed through as a model for saving? I assume the json would be something like
profile_attributes { [user_id:1 attribute_id:1 value:'hello world'], [user_id:1 attribute_id:2 value:''] }
I know the above form setup is not quite right but that is just one of several attempts I tried to see what it renders.
I tried:
<%= f.fields_for #user.profiles do |pp| %>
<% end %>
I even tried manually setting the field as arrays (similiar to asp.net mvc):
id=user[profiles][user_id]
id=user[profiles][attribute_id]
Controllers (they're empty shells at the moment, I just wanted to see the json output in the console)
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
end
class ProfileController < ApplicationController
def edit
#user = User.find(params[:id])
end
def update
respond_to do |format|
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(profiles_attributes: [:user_id, :attribute_id, :value])
end
end
end
I've tried many different approaches but with no success. Sometimes the form shows duplicated fields with same values, sometimes the form is shown but value is blank and upon submission complains about unpermitted parameter yet it was set in the controller.
Of course all the above can be done using javascript but I wanted to see if it was possible just using the model approach and nesting it.
You can do like this :
move form to your users/edit.html.erb and modify your users_controller as below
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def edit
#user.profiles.build
end
def update
if #user.update(user_params)
#redirect user where you want
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
def user_params
#permit user attributes along with profile attributes
end
end
Related
I have Categories (Parents) within which are listed Products (Children).
I want to be able to create a new Product directly from the navbar, anywhere in the app and then, during the creation, assign it to a Category.
However, I get the present error:
NoMethodError in Products#new
Showing /Users/istvanlazar/Mobily/app/views/products/new.html.erb where line #9 raised:
undefined method `products_path' for #<#<Class:0x00007febaa5aec98>:0x00007febae0f9e38>
Did you mean? product_show_path
## product_show_path is a custom controller that has nothing to do with this one,
enabling show and clean redirection from newest_products index bypassing categories nesting.
Extracted source (around line #9):
9 <%= form_for [#product] do |f| %>
10 <div class="form-styling">
11 <div>
12 <%= f.label :name %>
My App works as such:
Models
class Category < ApplicationRecord
has_many :products, inverse_of: :category
accepts_nested_attributes_for :products
validates :name, presence: true
end
class Product < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, inverse_of: :products
validates :category, presence: true
end
Routes
get 'products/new', to: 'products#new', as: 'new_product'
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :edit]
# resources :products, only: [:index, :show, :new, :edit]
end
Controllers
class ProductsController < ApplicationController
before_action :set_category, except: :new
def index
#products = Product.all
#products = policy_scope(#category.products).order(created_at: :desc)
end
def show
#product = Product.find(params[:id])
end
def new
#product = Product.new
#product.user = current_user
end
def create
#product = Product.new(product_params)
#product.user = current_user
if #product.save!
redirect_to category_product_path(#category, #product), notice: "Product has been successfully added to our database"
else
render :new
end
end
private
def set_category
#category = Category.find(params[:category_id])
end
def product_params
params.require(:product).permit(:name, :price, :description, :category_id, :user, :id)
end
end
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def show
#category = Category.find(params[:id])
end
def new
# Non-existant created in seeds.rb
end
def create
# idem
end
def edit
# idem
end
def update
# idem
end
def destroy
# idem
end
private
def category_params
params.require(:category).permit(:name, :id)
end
end
Views
# In shared/navbar.html.erb:
<nav>
<ul>
<li>Some Link</li>
<li>Another Link</li>
<li><%= link_to "Create", new_product_path %></li>
</ul>
</nav>
# In products/new.html.erb:
<%= form_for [#product] do |f| %>
<div class="form-styling">
<div>
<%= f.label :name %>
<%= f.text_field :name, required: true, placeholder: "Enter product name" %>
<%= f.label :price %>
<%= f.number_field :price, required: true %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div>
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, {prompt: 'Select a Category'}, required: true %>
</div>
<div>
<%= f.submit %>
</div>
</div>
<% end %>
Do you have any idea of where it went wrong? Or is it impossible to Create a Child before assigning it to a Parent..?
Thanks in advance.
Best,
Ist
You haven't defined any route to handle your new product form's POST. You've defined the new_product path, but this arrangement is breaking Rails' conventions and you're not providing a work-around.
You could define another custom route, e.g. post 'products', to: 'products#create', as: 'create_new_product' and then set that in your form like form_for #product, url: create_new_product_path do |f|.
However, you should consider changing the structure so that product routes are not nested under categories. Think twice before breaking conventions this way.
Edit: I misread the intention, ignore this. Go with Jeremy Weather's answer.
Or is it impossible to Create a Child before assigning it to a Parent..?
With the way you have your relationships and validations set up: yes, it is. A Product requires a Category, through the validates :category, presence: true. And if you're on Rails 5+, the association will already be required by default (meaning that validates is actually redundant). Meaning if you try to create a Product without a Category, it will fail, and the Product will not be saved to the database.
With that constraint in place, here's what I recommend trying:
Remove the custom /products/new route
remove get 'products/new', to: 'products#new', as: 'new_product'
Add :new and :create routes to allow product creation nested under categories
Add :create and :new, to the :only array to the resources :products nested under resources :categories
Move the file views/products/new.html.erb to views/categories/products/new.html.erb (creating a new categories directory in views if necessary).
In that file, alter the form_for so that the form is POSTing to the nested :create route:
form_for [#category, #product] do |f|
Visit the URL /categories/[insert some Category ID here]/products/new, filling out some data, and see if that works.
Your routes.rb should look like this:
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :new, :create, :edit]
end
Working with nested routes and associations. I have a partial which creates a tenant, but after the creation it stays with the form rendered and the url changes to /tenants. Desired behavior is that it needs to redirect_to the show page. Routes are as follows:
Rails.application.routes.draw do
devise_for :landlords
authenticated :landlord do
root "properties#index", as: "authenticated_root"
end
resources :tenants
resources :properties do
resources :units
end
root 'static#home'
end
So far the properties and units work (and the landlord) Issue is with Tenants. Originally I had Tenants nested under units, but had issues there as well. Partial looks like this:
<%= form_for #tenant do |f| %>
<%= f.label "Tenant Name:" %>
<%= f.text_field :name %>
<%= f.label "Move-in Date:" %>
<%= f.date_field :move_in_date %>
<%= f.label "Back Rent Amount:" %>
$<%= f.text_field :back_rent %>
<%= f.button :Submit %>
<% end %>
<%= link_to "Cancel", root_path %>
Tenants Controller looks like this:
before_action :authenticate_landlord!
#before_action :set_unit, only: [:new, :create]
before_action :set_tenant, except: [:new, :create]
def new
#tenant = Tenant.new
end
def create
#tenant = Tenant.new(tenant_params)
if #tenant.save
redirect_to(#tenant)
else
render 'new'
end
end
def show
end
def edit
end
def update
if #tenant.update(tenant_params)
redirect_to unit_tenant_path(#tenant)
else
render 'edit'
end
end
def destroy
end
private
def set_property
#property = Property.find(params[:property_id])
end
def set_unit
#unit = Unit.find(params[:unit_id])
end
def set_tenant
#tenant = Tenant.find(params[:id])
end
def tenant_params
params.require(:tenant).permit(:name, :move_in_date, :is_late, :back_rent, :unit_id)
end
end
Models have associations:
class Tenant < ApplicationRecord
belongs_to :unit, inverse_of: :tenants
end
class Unit < ApplicationRecord
belongs_to :property, inverse_of: :units
has_many :tenants, inverse_of: :unit
end
Lastly the show#tenants in rake routes is:
tenant GET /tenants/:id(.:format) tenants#show
I have extensively searched for this topic, but haven't had any success. Any help is appreciated. Rails 5.1
The route you are showing near the end of your question:
tenant GET /tenants/:id(.:format) tenants#show
is not the tenants index; it is the individual tenants/show route. You can tell this because it includes :id, which means it will show you a specific tenant having that id.
Try running rake routes again. The index route should look like this:
tenants GET /tenants(.:format) tenants#index
If you want to return to the tenants index after creating or updating a Tenant record, then you need to specify that path in your TenantsController. In both the #create and #update actions, your redirect line (after if #tenant.save and if #tenant.update, respectively) should read:
redirect_to tenants_path
That will take you to the TenantsController, #index action.
In the alternative, if you want to return to the individual tenant show page, then instead change both of those redirects in the TenantsController in both the #create and #update actions to:
redirect_to tenant_path(#tenant)
That will take you to the TenantsController, #show action for the current #tenant.
I am learning and building my first app with Ruby on Rails by cloning and adjusting an existing project. I got stuck in the writing a controller and hope that someone has a tip to help me out.
Context
I am building a trainings platform: Users can design a training, these trainings can be given on several dates (I call these trainingsessions 'thrills'), (other) users can subscribe to these thrills with reservations.
Conceptualization of models
Complication
I got the users, trainings and reservations model running now but I want to add the thrills model in between (I know this is not the easiest way, but I followed a training that did not include the thrills model). I setup the model and a simple view where a trainer should be able to add thrills to an existing training (in trainings/edit). Unfortunately after a day of trying I have not managed to do so, I keep getting:
NoMethodError in ThrillsController#create
NoMethodError (undefined method `thrills' for nil:NilClass): (line 16)
My TrainingsController for def edit looks like
def edit
if current_user.id == #training.user.id
#photos = #training.photos
#thrill = #training.thrills.create(thrill_params)
#thrills = #training.thrills
else
redirect_to root_path, notice: "Je hebt hier helaas geen toegang tot"
end
end
def update
if #training.update(training_params)
if params[:images]
params[:images].each do |image|
#training.photos.create(image: image)
end
end
#thrill = #training.thrills.create(thrill_params)
#thrills = #training.thrills
#photos = #training.photos
redirect_to edit_training_path(#training), notice: "Updated..."
else
render:edit
end
end
And my ThrillsController looks like
class ThrillsController < ApplicationController
def create
#thrill = #training.thrills.create(thrill_params)
redirect_to #thrill.training, notice: "Je thrill is aangemaakt!"
end
private
def thrill_params
params.require(:thrill).permit(:trilldate, :thrillhr, :thrillmin, :training_id)
end
end
And my form to add a thrill in views/thrills/_form.html.erb which is rendered in views/trainings/edit.html.erb
<%= form_for([#training.thrills.new]) do |f| %>
<div class="form-group">
<%= f.text_field :thrillhr, placeholder: "Uur", class: "form-control" %>
</div>
<%= f.hidden_field :training_id, value: #training.id %>
<div class="actions">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
The full code of the app can be found here https://github.com/chrisrutte/musc
Question
I'm obviously doing something simple wrong here, so I hope someone can provide me the hint how to save new thrills in my Thrills model.
If any more information is needed, don't hestitate to ask me.
Thanks in advance!
First lets look at what the models for this kind of setup would look like:
class Training < ActiveRecord::Base
has_many :thrills
has_many :users, through: :thrills
end
class Thrills < ActiveRecord::Base
belongs_to :training
has_many :reservations
has_many :users, through: :reservations
end
class Reservation < ActiveRecord::Base
belongs_to :user
belongs_to :thrill
has_one :training, though: :thrill
end
class User < ActiveRecord::Base
has_many :reservations
has_many :thrills, through: :reservations
has_many :trainings, through: :thrills
end
Make sure you read the Rails guide on associations as this is quite complex.
Notice that Reservation works as a join model.
Reservation also has an indirect relation to Training. This is to avoid having duplicate foreign keys on several levels. The same applies to the User model. The main reason is that ActiveRecord will only write the foreign key in once place!
When setting up the routes you will most likely want to use nesting:
resources :trainings, shallow: true do
resources :thrills
end
We can then setup the controller.
class ThrillsController < ApplicationController
before_action :set_training!, only: [:new, :create, :index]
before_action :set_thrill!, only: [:show, :edit, :update, :destroy]
# GET /trainings/:training_id/thrills/new
def new
#thrill = #training.thrills.new
end
# POST /trainings/:training_id/thrills
def create
#thrill = #training.thrills.new(thrill_params)
if #thrill.save
redirect_to #thrill
else
render :new
end
end
# GET /trainings/:training_id/thrills
def index
#thrills = #training.thrills
end
# this is not nested.
# GET /thrills/:id
def show
end
# this is not nested.
# GET /thrills/:id/edit
def edit
end
# this is not nested.
# PUT|PATCH /thrills/:id
def update
if #thrill.update(thrill_params)
redirect_to #thrill
else
render :edit
end
end
# ...
private
def set_training!
#training = Training.find(params[:training_id])
end
def set_thrill!
#thill = Thrill.joins(:training).find(params[:id])
#training = #thill.training
end
def thrill_params
params.require(:thrill)
.permit(:trilldate, :thrillhr, :thrillmin, :training_id)
end
end
And lets setup the form:
<%= form_for( [#training, #thrill] ) do |f| %>
<div class="form-group">
<%= f.text_field :thrillhr, placeholder: "Uur", class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
Since we want to pass the training_id though the form action attribute and not in the form body we use form_for( [#training, #thrill] ) which will give us the path /trainings/6/thrills for example.
However for our edit form we want /thrills/1 instead. So lets fix that:
<%= form_for( #thrill.new_record? ? [#training, #thrill] : #thrill ) do |f| %>
# ...
<% end %>
I am new to Ruby on Rails.I am facing a problem using nested resources.
I am building a learning app where there are courses and lessons.
Every course will have many lessons and a lesson belongs to only one course.
I am unable to create a lesson for a course currently.
Example : http://localhost:3000/courses/19/lessons/new is a page where i want to create and display lessons for course 19.
Routes.rb
Rails.application.routes.draw do
devise_for :users
resources :courses
resources :courses do
resources :lessons
end
resources :lessons
root 'pages#landing'
get 'pages/home' => 'pages#home' ,as: :home
get '/user/:id' => 'pages#profile',as: :profile
get '/users' => 'courses#index',as: :user_root
end
Course.rb
class Course < ActiveRecord::Base
belongs_to :user
has_many :lesson
validates :user_id , presence: true
end
Lesson.rb
class Lesson < ActiveRecord::Base
belongs_to :course
validates :course_id , presence: true
end
CourseController.rb
class CoursesController < ApplicationController
def index
#courses = Course.all;
end
def new
#course = Course.new;
end
def create
#course = Course.new(course_params);
#course.user_id = current_user.id;
if #course.save
redirect_to course_path(#course)
else
flash[:notice]="Course could not be created ! "
redirect_to new_course_path
end
end
def edit
end
def update
end
def destroy
#course = Course.find(params[:id]);
#course.destroy;
end
def show
#course = Course.find(params[:id]);
end
private
def course_params
params.require(:course).permit(:title, :description, :user_id)
end
end
LessonController.rb
class LessonsController < ApplicationController
def index
#lessons = Lesson.all;
end
def new
#lesson = Lesson.new;
end
def create
#lesson = Lesson.new(lesson_params);
#course = Course.find_by(id: [params[:course_id]]);
if #lesson.save
redirect_to new_course_lesson_path , flash[:notice] = "Lesson successfully saved !"
else
redirect_to new_course_lesson_path , flash[:notice] = "Lesson cannot be created ! "
end
end
def show
#lesson = Lesson.find(params[:id])
end
private
def lesson_params
params.require(:lesson).permit(:title,:description,:video,:course_id)
end
end
Lessonform.html.erb
<%= form_for ([#course,#lesson]) do |f| %>
<%= f.label :lesson_Title %>
<%= f.text_field :title ,placeholder: "Enter the lesson Title" ,:class=>"form-control" %><br />
<%= f.label :Description %>
<%= f.text_area :description ,placeholder: "Enter the lesson Description",rows:"8",:class=>"form-control" %><br />
<center>
<%= f.submit "Create lesson",:class =>"btn btn-lg btn-primary" %>
</center>
<% end %>
One problem i see is that you have defined route resources :lessons twice. Once, inside courses scope and second time outside.
The error seems to occur because in your view #course is nil. So, please check you set #course in a before_action inside lessons_controller#new action.
EDIT
class LessonsController < ApplicationController
before_action :set_course, only: [:new, :create]
def new
#lesson = #course.lessons.build
end
private
def set_course
#course = Course.find_by(id: params[:course_id])
end
end
Also replace has_many :lesson with has_many :lessons inside Course model.
First change you need to make in your Course model as you have singular lesson when defining many association:
has_many :lessons
Also let me know if their are any chances of lessons page being called without courses? If no then please remove:
resources :lessons
I guess also the two defining of courses in routes in creating issue. Please try removing the:
resources :courses
Let me know if you still face any issue.
I have three models: User, Publisher and Interest all with many to many relationships linked through three join models but only 2 out of 3 join models record the id's of their 2 parent models. my UsersPublisher model does not link User to Publisher.
My Interestscontroller proccesses a form (see code) through which I ask the user to provide Interest and Publisher. The latter gets processed via the fields_for method which allows you to pass Publisher attributes via the InterestsController. the UsersPublisher join model records the user_id but the publisher_id is nil.
I've tried putting #users_publishers in both the new and create methods of Publishers- and InterestsController. My latest attempt of using after_action in the InterestsController (see code) has also failed. I've also tried the after_action way in the PublishersController
Your helped is highly appreciated!
The UsersPublisher join model
class UsersPublisher < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
end
InterestsController
class InterestsController < ApplicationController
before_action :find_user
after_action :upublisher, only: [:new]
def index
#interests = policy_scope(Interest)
end
def show
#interest = Interest.find(params[:id])
end
def new
#interest = Interest.new
#interest.publishers.build
authorize #interest
end
def create
#interest = Interest.new(interest_params)
#users_interests = UsersInterest.create(user: current_user, interest: #interest)
authorize #interest
if #interest.save
respond_to do |format|
format.js
format.html {redirect_to root_path}
end
flash[:notice] = 'Thank you, we will be in touch soon'
else
respond_to do |format|
format.js { render }
format.html { render :new }
end
end
end
def edit
#interest = Interest.find(params[:id])
authorize #interest
end
def update
#interest = Interest.find(params[:id])
#interest.update(interest_params)
if #interest.save
flash[:notice] = 'Your interest has been added'
else
flash[:notice] = 'Oops something went wrong'
end
end
private
def interest_params
params.require(:interest).permit(:name, publishers_attributes: [:publisher,:id, :feed])
end
def find_user
#user = current_user
end
def upublisher
#users_publishers = UsersPublisher.create(publisher: #publisher, user: current_user)
end
end
Form
<%= form_for [#user, #interest] do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :publishers do |ff| %>
<%= ff.label :publisher %>
<%= ff.text_field :publisher %>
<%= ff.label :feed %>
<%= ff.text_field :feed %>
<%end%>
<%= f.submit "Submit" %>
<%end%>
Since you're using fields_for, you'll want to make sure you have accepts_nested_attributes_for:
class UsersPublisher < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
This should fix your issue (if it's as you outlined).
Your question is pretty broad, so I don't know whether the above will work. Below are my notes...
From the looks of it, your structure is very complicated; you should work to make it as simple as possible. In the case of creating "interests", you may wish to get rid of the form completely:
#config/routes.rb
resources :publishers do
resources :interests, path: "interest", only: [:create, :destroy] #-> url.com/publishers/:publisher_id/interest
end
#app/controllers/interests_controller.rb
class InterestsController < ApplicationController
before_action :set_publisher
def create
current_user.interests.create publisher: #publisher
end
def destroy
#interest = current_user.interests.find_by publisher_id: #publisher.id
current_user.interests.delete #interest
end
private
def set_publisher
#publisher = UserPublisher.find params[:publisher_id]
end
end
You'd be able to use the above as follows:
<%= link_to "Add Interest", publisher_interest_path(#publisher), method: :post %>
<%= link_to "Remove Interest", publisher_interest_path(#publisher), method: :delete %>
Thinking about it properly, you've got a pretty bad structure.
I'd do something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :interests
has_many :publishers, through: :interests
end
#app/models/interest.rb
class Interest < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
#app/models/publisher.rb
class Publisher < ActiveRecord::Base
has_many :interests,
has_many :users, through: :interests
end
This should give you the ability to create interests for any number of users and publishers. If you create a publisher for a specific user, you can use accepts_nested_attributes_for to pass the appropriate data:
#config/routes.rb
resources :users do
resources :interest, only: [:new, :create, :destroy] #-> url.com/users/:user_id/interests/new
end
#app/controllers/interests_controller.rb
class InterestsController < ApplicationController
def new
#user = User.find params[:user_id]
#interest = #user.interests.new
#interest.publisher.build
end
def create
#user = User.find params[:user_id]
#interest = #user.interests.new interest_params
end
private
def interest_params
params.require(:interest).permit(:user, :publisher)
end
end
#app/views/interests/new.html.erb
<%= form_for [#user, #interest] do |f| %>
<%= f.fields_for :publisher do |p| %>
<%= p.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>