Confused about default behavior of model initializer - ruby-on-rails

Given the following...
Model:
class Equipment < ApplicationRecord
belongs_to :user
end
Controller:
class EquipmentController < ApplicationController
before_action :set_equipment, only: %i[ show edit update destroy ]
# GET /equipment/new
def new
#equipment = Equipment.new
end
# POST /equipment or /equipment.json
def create
#equipment = Equipment.new(equipment_params)
respond_to do |format|
if #equipment.save
format.html { redirect_to [#equipment.user, #equipment], notice: "Equipment was successfully created." }
format.json { render :show, status: :created, location: #equipment }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #equipment.errors, status: :unprocessable_entity }
end
end
end
Test:
class EquipmentControllerTest < ActionDispatch::IntegrationTest
setup do
#equipment = equipment(:one)
sign_in_as(#equipment.user)
end
test "should get new" do
get new_user_equipment_url(#equipment.user.id)
assert_response :success
end
My test fails here, because equipment.user is null.
<%= form_with(model: [equipment.user, equipment]) do |form| %>
However, when I visit the corresponding URL in the development environment in the browser, it works. Debugging the development environment, the Equipment.new sets the user on the new equipment object. However, when running the test, the user is not set.
Making this change in the controller fixes the issue, but I don't understand the difference in behavior between the integration test and using the browser. Can someone explain?
#equipment = Equipment.new user: Current.user

I think this would be a lot less confusing if you wrote the test properly:
class EquipmentControllerTest < ActionDispatch::IntegrationTest
setup do
#user = users(:one)
sign_in_as(#user)
end
test "should get new" do
get new_user_equipment_url(#user)
assert_response :success
end
end
You should not use the equipment fixture when you're testing the new/create action - after all what your test is supposed to cover is the form for a completely new resource. The equipment fixture would be used when showing and updating an existing equipment.
Then I think you're falling for a classic trap and confusing the #equipment instance variable in your test and the instance variable in your controller with the same name.
Remember that your test and the controller are completely separate instances of different classes - sometimes even running in a completely different processes. Data doesn't just magically teleport itself from the test to the controller - it has to be passed through parameters or be loaded from the database.
#equipment in your controller is just a new Equipment instance that you have created there which doesn't have a user assigned. Thats why you need to assign the user.
Note that unless your users need to be able to create equipment for other users you don't actually need to have a nested route like this. You could just use GET /equipment/new and fetch the user from the session (or a token) instead of a parameter.
For example given a typical Devise setup you would do:
class User < ApplicationRecord
has_many :equipment
end
class EquipmentController < ApplicationController
before_action :set_equipment, only: %i[ show edit update destroy ]
before_action :authenticate_user! # ensure that a user is logged in.
# POST /equipment or /equipment.json
def create
#equipment = current_user.equipment.new(equipment_params)
respond_to do |format|
if #equipment.save
format.html { redirect_to #equipment, notice: "Equipment was successfully created." }
format.json { render :show, status: :created, location: #equipment }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #equipment.errors, status: :unprocessable_entity }
end
end
end

Related

Redirect to a post with specific name from user input in Ruby on Rails?

I am a newbie in RoR, thus sorry for stupid question :(
I have a Game model, with a code string. There is a welcome/index view in my app with a simple form_to input. I wish to redirect user to a Game with a specific code after he submits the form.
I understand that I should somehow combine a .where method and redirect_to in Welcome_controller, but I just can't figure out how...
Welcome_controller.rb:
class WelcomeController < ApplicationController
def index
end
def redirect
redirect_to ?game with a code that equals :param from input?
end
end
Welcome/index:
<h1>Let's join the game!</h1>
<%= form_tag redirect_path do %>
<%= text_field_tag(:param) %>
<%= submit_tag("Search") %>
<% end %>
routes.rb:
Rails.application.routes.draw do
get 'welcome/index'
resources :games
get 'games/index'
root 'welcome#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
game.rb:
class Game < ApplicationRecord
validates :name, :presence => true
end
games_controller:
PREFACE = ('A'..'Z').to_a << ?_
SUFFIX = ('0'..'9').to_a
PREFACE_SIZE = 2
SUFFIX_SIZE = 3
class GamesController < ApplicationController
before_action :set_game, only: %i[ show edit update destroy ]
# GET /games or /games.json
def index
#games = Game.all
end
# GET /games/1 or /games/1.json
def show
end
# GET /games/new
def new
#game = Game.new
#game.code = gen_name
end
def gen_name
PREFACE.sample(PREFACE_SIZE).join << SUFFIX.sample(SUFFIX_SIZE).join
end
# GET /games/1/edit
def edit
end
# POST /games or /games.json
def create
#game = Game.new(game_params)
respond_to do |format|
if #game.save
format.html { redirect_to game_url(#game), notice: "Game was successfully created." }
format.json { render :show, status: :created, location: #game }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /games/1 or /games/1.json
def update
respond_to do |format|
if #game.update(game_params)
format.html { redirect_to game_url(#game), notice: "Game was successfully updated." }
format.json { render :show, status: :ok, location: #game }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
# DELETE /games/1 or /games/1.json
def destroy
#game.destroy
respond_to do |format|
format.html { redirect_to games_url, notice: "Game was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_game
#game = Game.find(params[:id])
end
# Only allow a list of trusted parameters through.
def game_params
params.require(:game).permit(:code, :name)
end
end
In config/routes.rb you have defined resources :games, which creates default paths for CRUD actions. For the show action, which you are trying to get here, it would lead to /games/:id and the helper method would be game_path. You can also check this by running rails routes -c games command in the app directory. It should return all paths for games_controller
In the before_action callback for GamesController#show action, you are finding a Game object using Game.find(params[:id]). :id parameter is what you need to pass to the path helper that I mentioned earlier for the action to fire properly, so the path to a specific game would look like game_path(id: game.id). This will then automatically get converted to params. Alternatively, you can just pass the game object to the path helper and it will do the job for you like this: game_path(game)
Now in the WelcomeController#redirect action, you get the game code in params from the form submit. You need to first find the game for the submitted code like this:
game = Game.find_by(code: params[:param])
This should work if the code is unique for each game. Now that you have the correct game record, all you need is to redirect to the path that I've mentioned eariler:
redirect_to game_path(game)

How to display a user's posts only. Not others

This app is for tutors. When a tutor finishes a class, they fill out a class_report. Then the index page should display only their class_reports.
Here's my problem: I've made two accounts, test1 and test2. test1 can see both test1 and test2's class_reports but test2 cannot see any posts, not even their own. It is even saying a post was created by test1 when test2 created it.
I'm pretty sure something is off in the index part or create part of the class_reports_controller but I am not entirely sure tbh. I think it could also be in the models as well.
class_reports_controller.rb
class ClassReportsController < ApplicationController
before_action :require_login
before_action :set_class_report, only: [:show, :edit, :update, :destroy]
# GET /class_reports
# GET /class_reports.json
def index
#class_report = current_user.class_reports
end
def create
#class_report = ClassReport.new(class_report_params)
#class_report.user = User.first
respond_to do |format|
if #class_report.save
format.html { redirect_to #class_report, notice: 'Class report was successfully created.' }
format.json { render :show, status: :created, location: #class_report }
else
format.html { render :new }
format.json { render json: #class_report.errors, status: :unprocessable_entity }
end
end
end
Models:
class_report.rb
class ClassReport < ApplicationRecord
belongs_to :user
end
user.rb
class User < ApplicationRecord
include Clearance::User
has_many :class_reports
before_save { self.email = email.downcase }
end
There is something wrong in your create action, on this line:
#class_report.user = User.first
and change it to:
#class_report.user = current_user
So the problem with first line is that all the report is created and link to first user (always), that's why other user doesn't have the reports. By changing to second line, we create a report and link to the logged-in user (current_user), therefore the report is created and assigned to whoever logged-in and create report.
Hope it help.

add automatically value to column database in rails

notes use rails 5.2 and postgresql
I have Foluser model contains name,email,password,id_watch
I need when admin add new foluser
generate password
when admin create new foluser generate password like Secure Password Generator
get id_watch from admin model and put it to id_watch from Foluser model
Adminwhen register enterusername,email,password,id_watch`
in point 2 need take this id_watch and save it in user model .
admin only create foluser
`
class FolusersController < ApplicationController
before_action :set_foluser, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show, :new , :create, :edit]
# GET /folusers
# GET /folusers.json
def index
#folusers = current_master.foluser.all
#render json: #folusers
end
# GET /folusers/1
# GET /folusers/1.json
def show
##folusers = Foluser.where(master_id: #master.id).order("created_at DESC")
##foluser = Foluser.find(params[:id])
#render json: #foluser
end
# GET /folusers/new
def new
#foluser = current_master.foluser.build
end
# GET /folusers/1/edit
def edit
#render json: #foluser
end
# POST /folusers
# POST /folusers.json
def create
#foluser = current_master.foluser.build(foluser_params)
respond_to do |format|
if #foluser.save
format.html { redirect_to #foluser, notice: 'Foluser was successfully created.' }
format.json { render :show, status: :created, location: #foluser }
else
format.html { render :new }
format.json { render json: #foluser.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /folusers/1
# PATCH/PUT /folusers/1.json
def update
respond_to do |format|
if #foluser.update(foluser_params)
format.html { redirect_to #foluser, notice: 'Foluser was successfully updated.' }
format.json { render :show, status: :ok, location: #foluser }
else
format.html { render :edit }
format.json { render json: #foluser.errors, status: :unprocessable_entity }
end
end
end
# DELETE /folusers/1
# DELETE /folusers/1.json
def destroy
#foluser.destroy
respond_to do |format|
format.html { redirect_to folusers_url, notice: 'Foluser was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_foluser
#foluser = Foluser.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def foluser_params
params.require(:foluser).permit(:name, :email, :numberphone, :password)
end
end
foluser model
class Foluser < ApplicationRecord
belongs_to :admin, :optional => true
end
admin model
class Master < ApplicationRecord
has_many :foluser
end
Using your current code, setting the id_watch can be done here in the controller:
class FolusersController < ApplicationController
def create
#foluser = current_master.folusers.build(foluser_params)
#foluser.id_watch = current_master.id_watch # <-- !!!
respond_to do |format|
if #foluser.save
# ...
end
end
end
end
Despite our extended conversation above, I'm still unclear what you're trying to achieve with the "password generation".
(Should it be generated in the front-end, or the back-end? Should it be stored encrypted, or in plain text? If encrypted, do you need to be able to reverse this encryption? Is it a "permanent" password, or a "temporary" password? ...)
Therefore, the following code should be taken with a big pinch of salt - since I still don't really know what the desired/correct behaviour is.
In the FolusersController, you've defined the following method:
def foluser_params
params.require(:foluser).permit(:name, :email, :numberphone, :password)
end
However, if you want the password to be generated by the server then you shouldn't be allowing the admin to set the password through the controller. Therefore, remove this parameter:
def foluser_params
params.require(:foluser).permit(:name, :email, :numberphone)
end
And then somewhere - perhaps in the controller, or as a hook in the model - set this password to something random:
class FolusersController < ApplicationController
def create
#foluser = current_master.folusers.build(foluser_params)
#foluser.password = SecureRandom.hex(10 + rand(6))
# ...
end
end
# or
class Foluser < ApplicationRecord
after_initialize :default_password
def default_password
self.password ||= SecureRandom.hex(10 + rand(6))
end
end
I think you found the solution, use rails callbacks in your model to extract this kind of logic from the controller.
But I'd rather use after_initialize than before_save so that you won't set a default password before each save(so possibly even update action)
Then use things like SecureRandom (ActiveSupport concern) (already bundled by rails, no requires required)
after_initialize :defaultpassword
...
def default_password
self.password ||= SecureRandom.hex(10 + rand(6))
end
not the best way to do random I know but feel free to customize it.
secure_random output examples:
=>bf8d42b174d297f6460eef
=>efd28869171a1ec89c3438
=>3855c61fb6b90ed549d777

Rails - how to reference namespaced table attributes in views

I am trying to learn how to use namespaces in my Rails 5 app.
I have an organisation model and I have also made a series of nested models under the folder name "stance". One of those models is called overview.
The associations are:
Organisation.rb
has_one :overview, class_name: Stance::Overview
accepts_nested_attributes_for :overview, reject_if: :all_blank, allow_destroy: true
Stance::Overview
class Stance::Overview < ApplicationRecord
belongs_to :organisation, inverse_of: :overview
My controllers for stance resources are nested under a folder called stance.
My routes are:
namespace :stance do
resources :overviews
end
In my stance view partial, I am trying to render the attributes from the overview table.
I have tried:
<p><%= #overview.internal_explanation %></p>
<p><%= #stance_overview.internal_explanation %></p>
<p><%= #stance.overview.internal_explanation %></p>
<p><%= #stance::overview.internal_explanation %></p>
I want to display this partial in my organisation show. I am trying to do that with:
<%= render 'stance/overviews/internal', overview: #overview %>
But I can't figure out how to access the overview table. Do I need to add a reference to 'stance' in the associations?
I can see that in the console I need to write:
o = Stance::Overview.create(internal_explanation: "test")
o = Stance::Overview.first
but I can't see how to use that in the code itself.
I can see in the console that there is a record for this attribute.
The name of the table in the schema is "stance_overview".
My organisation controller has:
class OrganisationsController < ApplicationController
before_action :set_organisation, only: [:show, :edit, :update, :destroy]
def index
#organisations = Organisation.all
end
def show
end
def new
#organisation = Organisation.new
#organisation.build_overview
end
def edit
#organisation.build_overview unless #organisation.overview
end
def create
#organisation = Organisation.new(organisation_params)
respond_to do |format|
if #organisation.save
format.html { redirect_to #organisation, notice: 'Organisation was successfully created.' }
format.json { render :show, status: :created, location: #organisation }
else
format.html { render :new }
format.json { render json: #organisation.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #organisation.update(organisation_params)
format.html { redirect_to #organisation, notice: 'Organisation was successfully updated.' }
format.json { render :show, status: :ok, location: #organisation }
else
format.html { render :edit }
format.json { render json: #organisation.errors, status: :unprocessable_entity }
end
end
end
def destroy
#organisation.destroy
respond_to do |format|
format.html { redirect_to organisations_url, notice: 'Organisation was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_organisation
#organisation = Organisation.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def organisation_params
params.fetch(:organisation, {}).permit(:title, :comment,
overview_attributes: [:internal_explanation, :external_explanation ]
)
end
end
I have also tried defining the strong params for organisation as:
stance_overview_attributes: [:internal_explanation, :external_explanation ]
I keep getting an error that says:
undefined method `internal_explanation' for nil:NilClass
Can anyone refer me to materials to help me learn how to use namespaces in my app. I am trying to understand the fundamentals of this so that I can bank some knowledge. I am finding things through trial and error but not understanding what's actually required (although in this case, none of my attempts are working out).
To access Overview model(table) when you working not in Stance namespace you have to use Stance::Overview. If working for example in a controller that in Stance namespace you can use just Overview for access.
To get access from the relation you don't need any additional notation just #organisation.overview.
If I understand correctly in you case you have to declare your partial as
<%= render 'stance/overviews/internal', overview: #organisation.overview %>
and in the partial you have to use overview without #.

How to use POST with ruby on rails?

I created a blog application using rails. I'm trying use that as an API for a mobile app. I'm trying to write JSON to POST the content into the application.
#model/post.rb
class Post < ActiveRecord::Base
has_many :comments
belongs_to :category
end
#model/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
end
#model/category.rb
has_many :posts
#controllers/comments_controller
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /comments/new
def new
#comment = Comment.new
end
# GET /comments/1/edit
def edit
end
# POST /comments
# POST /comments.json
def create
#comment = Comment.new(comment_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'comment was successfully created.' }
format.json { render :show, status: :created, location: #comment }
else
format.html { render :new }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:rating, :comment, :post_id, :user_id)
end
end
What will be the JSON for the POST request for posting a blog post and a comment?
How should I pass association through JSON?
Here is the route for that action.
POST /comments(.:format) comments#create
If you want to be able to create a post and comment in the same request you will need to use accepts_nested_attributes_for.
class Post
has_many :comments
accepts_nested_attributes_for :comments
end
The params to create a post and a comment would look something like this:
{
"post" : {
"title": "Hello World",
"comments_attributes": [
{
"rating": 3,
"comment": "Pretty mediocre if you ask me."
}
]
}
}
Rails does a pretty good job at abstracting out the difference between parameters sent as application/x-www-form-urlencoded, multipart or JSON. Dealing with JSON input is just like dealing with regular form input.
Then in your PostsController you would need to whitelist the nested attributes:
class PostsController < ApplicationController
# POST /posts
def create
#post = Post.new(post_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #post, notice: 'comment was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
private
def post_params
params.require(:post).allow(:title, comments_attributes: [:rating, :comment])
end
end
If you want your blog app to provide an API for external apps to use, you'll have to create an API layer within your application. This exposes endpoints that other apps can use to retrieve/post information to/from your site.
One of the first steps would be to scope out an api layer in your routes.rb:
YourBloggingApp::Application.routes.draw do
namespace '/api', defaults: {format: 'json'} do
scope '/v1' do
scope '/blog_posts' do
get '/' => 'api_blog_posts#index'
post '/' => 'api_blog_posts#create'
...
end
end
end
end
Then you can build the appropriate actions within your api controllers:
class Api::BlogPostsController < ApplicationController
def index
#blog_posts = BlogPost.all
end
def create
#blog_post = BlogPost.create(title: params[:title], description: params[:description])
end
...
end
And when a user from your mobile app fills out a form, the form can send a POST request to https://yourappdomain.com/api/v1/blog_posts/, the blog_posts_controller#create action is triggered, which allows you to then pass in the given params sent through the request to create blog posts, comments, or whatever you set up in your project.
This is a very generic example, and there are a lot of specific details to cover on this topic, but this AirPair article provides a decent starting point for building a RESTful API.
{"comment":{"comment":"blah blah blah", "post_id":1}}
I have to pass the post_id in that POST request.

Resources