Creating a second form page for a has_many relationship - ruby-on-rails

I have an Organization model that has_many users through affiliations.
And, in the form of the organization ( the standard edit ) I use semanting_form_for and semantic_fields_for to display the organization fields and affiliations fields.
But I wish to create a separete form just to handle the affiliations of a specific organization. I was trying to go to the Organization controller and create a an edit_team and update_team methods then on the routes create those pages, but it's getting a mess and not working.
am I on the right track?

Yes, you should create edit_team and update_team methods in controller and add them into routes.rb
#organizations_controller
def edit_team
#organization = Organization.find(params[:id])
#team = #organization.affiliations
end
def update_team
# updating affiliations
end
#routes.rb
map.resources :organizations, :member => { :edit_team => :get, :update_team => :put }
and this is enough. So show errors why it isn't working.

Related

How do I make my redirect include the name instead of an ID?

I'm using Rails 5.1. In my controller, I would like to redirect to my "show" method like so
redirect_to(#organization)
but I would like the URL to appear as
/organization/organization_name
instead of
/organization/primary_key_id
How do I set this up? I already have a field "name" in my Organization model.
Edit: As requested, this is the index method of my PagesController ...
class PagesController < ApplicationController
# Should be the home page
def index
worker_id = params[:worker_id]
worker = Worker.find_by_id(worker_id)
if worker && worker.organization
redirect_to(worker.organization)
else
render :file => "#{Rails.root}/public/404", layout: false, status: 404
end
end
end
Edit: My config/routes.rb file
resources :organizations, :only => [:show] do
post :update_work
get :get_work
get :mine
get :poll
post :submit
get :home
get :terms_of_use
end
Here's the app/model/stratum_worker.rb file
class StratumWorker < ApplicationRecord
has_one :organization_worker
has_one :organization, :through => :organization_worker
OK, if you are not interested to use any gem then you can without gem like
class Model < ApplicationRecord
def to_param # overridden
organization_name
end
end
in this case, you need to make sure the organization_name name is unique, for uniqueness the organization_name you can use validation like this
validates_uniqueness_of :organization_name
then the model will look like this
class Model < ApplicationRecord
validates_uniqueness_of :organization_name
def to_param # overridden
organization_name
end
end
and now to the controller using find_by_organization_name(params[:organization_name]) instead of find(params[:id]).
Second Option
You can not change anything to your controller if used like this in just model
class Model < ApplicationRecord
def to_param # overridden
organization_name
"#{id} #{organization_name}".parameterize
end
end
then the URL looks like this /10-microsoft.
See this to_param method. The complete reference of with gem or without gem Rails Friendly URLs
RailsCasts.com created an episode for Pretty URLs with FriendlyId, can you check it out for getting the idea.
From Comment
I don't think what's going on but sure something wrong with the relationship, can you check like this
redirect_to(worker.organizations.first)
#=> OR
redirect_to organization_path(worker.organizations.first.id)
Update
I think worker.organization are missing somehow, would you try like this?
if worker && worker.organizations.present?
redirect_to(worker.organizations.first)
....
the present method making sure worker.organizations not blank.
I don't know about the relationship, you can try like this and let me know what's happening if it's not working then I strongly recommend to post the models with relationship concept.
Update 2 after question update
At first, you don't need the through relationship because it uses too Many To Many relationships. Your relationship is One To One then your model will look like this
class StratumWorker < ApplicationRecord
has_one :organization_worker
....
has_one :organization, :through => :organization_worker
organization_worker.rb file like this
class OrganizationWorker < ApplicationRecord
belongs_to :stratum_worker
#=> Add code what you need like for URL which was the actual motive in this post
....
Then the action looks like this
def index
worker_id = params[:worker_id]
worker = StratumWorker.find_by_id(worker_id)
if worker && worker.organization_worker.present?
#redirect_to(worker.organization_worker)
redirect_to organization_path(worker.organization_worker)
else
render :file => "#{Rails.root}/public/404", layout: false, status: 404
end
end
and the show action
OrganizationWorker.find(params:id)
I think the problem will solve now. If still, you getting errors then please read the One To One relationship again & again until clearing the relationship concept.
Hope it will help.
I wrote a post here detailing exactly this a while ago. Most of my answer will be from there. The relevant Rails documentation for this is here.
Quick definitions:
Slug: part of the URL to identify the record, in your case organization_name
Primary key: a unique identifier for database records. This usually is and should be id.
Summary
If you type organization_path(#organization), it'll automatically use the id attribute in the URL. To adjust to using organization_name, you'll need to make 2 changes:
Override the route params in your routes.rb file.
Override the to_param method in the model
1. Override The Route Params
At the moment, if you run rails routes your routes look like so:
organizations GET /organizations(.:format) organizations#index
POST /organizations(.:format) organizations#create
new_organization GET /organizations/new(.:format) organizations#new
edit_organization GET /organizations/:id/edit(.:format) organizations#edit
organization GET /organizations/:id(.:format) organizations#show
PATCH /organizations/:id(.:format) organizations#update
PUT /organizations/:id(.:format) organizations#update
DELETE /organizations/:id(.:format) organizations#destroy
The edit_organization and organization paths use id as a parameter to lookup your organization.
Use this to override the route params
Rails.application.routes.draw do
resources :organizations, param: :organization_name
end
Now rails routes will show that your routes look like so:
organizations GET /organizations(.:format) organizations#index
POST /organizations(.:format) organizations#create
new_organization GET /organizations/new(.:format) organizations#new
edit_organization GET /organizations/:organization_name/edit(.:format) organizations#edit
organization GET /organizations/:organization_name(.:format) organizations#show
PATCH /organizations/:organization_name(.:format) organizations#update
PUT /organizations/:organization_name(.:format) organizations#update
DELETE /organizations/:organization_name(.:format) organizations#destroy
2. Override The Model Params
By default organization.to_param will return the id of the organization. This needs to be overridden, do this by modifying your Model:
class Organization < ApplicationRecord
def to_param
organization_name
end
end
Conclusion & Warning
You can now continue using your redirects and forms as usual, but instead of the route using the id, it'll now use the organization name.
Also, good luck with your mining pool! Lemme know which coin you're mining and I might join!
Also, I didn't cover this because it isn't a part of your original question, but, you should ensure that the organization_name is unique! Not only should you add a uniqueness constraint validates :organization_name, uniqueness: true in the mode, you should also enforce it at the database level in your migration.
Addendum 1: Customizing for routs
When your routes are defined as so:
resources :organizations, :only => [:show] do
post 'update_work'
get 'get_work'
get 'mine'
get 'poll'
post 'submit'
get 'home'
get 'terms_of_use'
end
Your routes will be as so:
organization_update_work POST /organizations/:organization_id/update_work(.:format) organizations#update_work
organization_get_work GET /organizations/:organization_id/get_work(.:format) organizations#get_work
organization_mine GET /organizations/:organization_id/mine(.:format) organizations#mine
organization_poll GET /organizations/:organization_id/poll(.:format) organizations#poll
organization_submit POST /organizations/:organization_id/submit(.:format) organizations#submit
organization_home GET /organizations/:organization_id/home(.:format) organizations#home
organization_terms_of_use GET /organizations/:organization_id/terms_of_use(.:format) organizations#terms_of_use
organization GET /organizations/:id(.:format) organizations#show
Changing the param like so:
resources :organizations, :only => [:show], param: :organization_name do
post 'update_work'
get 'get_work'
get 'mine'
get 'poll'
post 'submit'
get 'home'
get 'terms_of_use'
end
Will change your routes to
organization_update_work POST /organizations/:organization_organization_name/update_work(.:format) organizations#update_work
organization_get_work GET /organizations/:organization_organization_name/get_work(.:format) organizations#get_work
organization_mine GET /organizations/:organization_organization_name/mine(.:format) organizations#mine
organization_poll GET /organizations/:organization_organization_name/poll(.:format) organizations#poll
organization_submit POST /organizations/:organization_organization_name/submit(.:format) organizations#submit
organization_home GET /organizations/:organization_organization_name/home(.:format) organizations#home
organization_terms_of_use GET /organizations/:organization_organization_name/terms_of_use(.:format) organizations#terms_of_use
organization GET /organizations/:organization_name(.:format) organizations#show
Which should work totally fine with your redirect.
Method that is called under the hood for id generation is to_param
so in your case to get your desired result you should add this to your Organization class:
class Organization < ApplicationRecord
...
def to_param
name
end
...
end
!!!WARNING!!! - since Rails is also using the parameter on the other side (e.g. in show method Organization.find(params[:id]) uses the URL id), now it will be params[:id] == "some_organization_name" so change your instance lookups accordingly - in show action for example use Organization.find_by!(name: params[:id]) and so on
As for your routing error - make sure that worker.organization is not nil.
There is a gem friendly_id that does exactly what you are asking for: https://github.com/norman/friendly_id
You add,
gem 'friendly_id'
Then bundle install and run rails generate friendly_id and rails db:migrate
to your Gemfile and,
class Organization < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
end
to your model then,
class OrganizationController < ApplicationController
def show
#user = Organization.friendly.find(params[:id])
end
end
to your controller.
This prevents the issues you can run into in Kkulikovskis answer where you have to make sure that you are looking things up correctly.

Creating a record from a different model view(no new action involved)

I have a has_many & belongs_to associations between company and worker. I want to be able to add a worker to a company via a link in my workers index page. I want that to save the worker record belonging to the logged in company and update the company/workers index page with the new worker added to their workers.
I have been unable to do this. Here is what is happening:
My routes have something like:
namespace :company do
resources :workers, :only => [:index, :create]
end
resources :workers
I have a before_action method that has the cookie session with the company access token in my controllers:
ApplicationController:
#company = Company.find_by_access_token("vZAni6K6")
cookies[:access_token] = #company.access_token
And Company::WorkersController
render :file => "public/401.html", :layout => nil, :status => :unauthorized and return if cookies[:access_token] != #company.access_token
In my workers/index.html.haml path I have:
= "There are #{#workers.count} workers!"
They are:
- #workers.each do |worker|
= worker.name
= link_to "Hire a worker", company_workers_path(:worker_id => worker), :method => :post
%%br
%p= #company.name
I want the to be able to click the "Hire a worker link" and then be redirected to the company/workers.html.haml path that then will list their workers updated with the recent addition of the worker just added via the link.
When I currently click the link(say for worker #2 in by database) instead of taking me to company/workers path it takes me to
company/workers?worker_id=2
And it doesn't save the worker to the association with the company.
My company/workers controller has the following:
def create
#worker = #company.worker.build(:worker_id => params[:worker_id])
#worker.save
redirect_to :action => 'index'
end
Remember I have a before_action on my controllers that saves the #company instance variable before calling other controller methods as well.
I have a model Worker that belongs_to a company & a model Company that has_many workers and I have added the reference key in my migration already.
What is the problem? Why the weird route and why are my records not saving, I am a bit of a newb so forgive the simple question.
At first look, I see you may have two errors:
your Company model class has_many workers, so to assign (or add) a worker to a company I believe it must be something like:
#worker = Worker.find(params[:worker_id])
#worker.company = #company
#worker.save!
# depending in your schema, it could be something like this as well:
# #company.workers << #worker
to redirect to company/workers, as you use nested routes, it must be like
redirect_to company_workers_path(#company)
Hope this helps!

Neo4J Gem - Saving undeclared relationships

I am trying to create a realtionship between two nodes as described here
https://github.com/neo4jrb/neo4j/wiki/Neo4j-v3-Declared-Relationships
from_node.create_rel("FRIENDS", to_node)
I am getting an undefined method for create_rel
What am I doing wrong? I am trying to create a Q+A system inside another model. So both Questions and Answers are treated as models right now.
I'm getting a undefined methodcreate_rel' for #
event.rb
has_many :out, :event_questions
event_question.rb
has_one :in, :events
has_many :out, :event_answers
def create_questions_of(from_node,to_node)
from_node.create_rel("questions_of", to_node)
end
event_answer.rb
has_one :in, :event_questions
event_questions_controller.rb
def new
#is this needed
end
def create
#event_question = EventQuestion.new(event_question_params)
if #event_question.save
#event = Event.find(params[:id])
#event_question.update(admin: current_user.facebook_id)
#event_question.create_questions_of(self,#event)
redirect_to #event
else
redirect_to #event
end
end
private
def event_question_params
params.require(:event_question).permit(:question)
end
I have my new question sitting inside the event's index page since I wanted to list all the questions on the event after. I don't even need a new method in my controller right? I also don't really know how I would obtain the event that my question form is sitting on. Is that accessible through params?
UPDATE
Did you mean this
def create_questions_of(to_node)
self.create_rel("questions_of", to_node)
end
and
#event_question.create_questions_of(#event)
So I think I need to change my routes as well and nest questions inside to create
events/123/questions/
Then I can grab events_id and use find
UPDATE #2
events_controller.rb
def show
#event = Event.find(params[:id])
#event_question = EventQuestion.new
end
event.rb
has_many :out, :event_questions, type: 'questions_of'
event_question.rb
has_one :in, :events, origin: :event_questions
events/show.html.erb
<%= form_for [:event, #event_question] do |f| %>
#form stuff
<% end %>
event_questions_controller.rb
def create
#event_question = EventQuestion.new(event_question_params)
if #event_question.save
#event = Event.find(params[:event_id])
#event_question.update(admin: current_user.facebook_id)
#event_question.events << #event
redirect_to #event
else
redirect_to :back
end
end
routes.rb
resources :events do
resources :event_questions, only: [:create, :destroy]
end
create_rel worked fine when I tested it just now. Is it saying undefined method 'create_rel' for nil:NilClass? If so, it means that your from_node variable doesn't actually have a node set. Make sure your objects are what you think they are.
The better question here: why do you want to do this? When you create an undeclared relationship, you have to write your own Cypher queries whenever you want to use it. If it's part of your code and you are using it regularly, it should probably have has_many associations in your models. create_rel really only exists to provide interoperability with nodes that don't have models.
As for your other question, you don't need a new action unless there's a route and a view that corresponds with it. If you're loading the form for a new question on your index page, that's fine. If your URL is something like http://127.0.0.1:3000/events/123/questions/, then you can get the Event ID in params[:event_id]. Run the rake routes command from your project's directory and it'll spit out lots of information that includes the parameter names.
Finally, when you use self in #event_question.create_questions_of(self,#event), you're going to get the controller. If you want it to refer to the #event_question, just remove that first argument from create_questions_of and use self from within the method.
Edit: Part 2
You're getting the undefined method because self in #event_question.create_questions_of(self,#event) is the controller. You're trying to send #event_question to itself, I think. Don't do that, just call self from within create_questions_of and you'll get current EventQuestion.
You use ActiveRel if you want callbacks, validations, properties, etc,... If you just want a simple relationships, just setup the has_many associations in each model, omit rel_class, and either set them both to the same type or set origin on one.
class Event
include Neo4j::ActiveNode
has_many :in, :event_questions, type: 'questions_of'
end
class EventQuestion
include Neo4j::ActiveNode
has_many :out, :events, origin: :event_questions
end
origin says, "Look for this association in the reciprocal model and use the type it defines." It lets you not have to worry about synchronizing the type between associations.
After that, you can do #event_question.events << #event and it'll create a new relationship for you.

How to manage nested routes and controllers in rails?

We have multiple models Post, Blog, Wiki and Comment.
In the comment table we maintain object_type, object_id and comment_type.
Comment table data
id object_type object_id comment_type
1 'Post' 1 'System'
2 'Blog' 2 'System'
3 'Wiki' 3 'User'
4 'Wiki' 4 'System'
/post/1/comments/:comment_type
/wiki/1/comments/:comment_type
To handle this, How should my routes should look like and how many controller should I create to handle different comment types ?
You can get the parent model from the url:
before_filter :get_commentable
def create
#comment = #commentable.comments.create(comment_params)
respond_with #comment
end
private
def get_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.pluralize.classify.constantize.find(id)
end
As for this:
/post/1/comments/:comment_type
nesting the resources as in so:
resources :posts do
resources :comments
end
gives you:
post_comment_path GET /posts/:post_id/comments/:id(.:format) comments#show
you can do the same thing for the other resources.
UPDATE:
about selecting comment_type, for example, one way to handle it is to create a separate model.
class Comment
has_and_belongs_to_many :comment_types
end
class CommentType
has_and_belongs_to_many :comments
end
i you are using simple_form, in your new.html.erb form, you will do this:
<%= f.association :comment_types %>
this will give you a drop down to select the comment_types. you can create the comment types in your console. say you have only: "system" and "user" comment_types. simply create those in the console and they will both show up in the drop down for you to select.
If you take this approach, you don't need to nest the resources in your routes.rb file.

Rails restful routes problem

I've got two models: Book and ReadingList. A ReadingList has_and_belongs_to_many Books. On the BooksController#show page, I'd like to have a select list that shows all the reading lists, with a button to add the current book to the selected reading list.
Presumably this should go to the ReadingListController#update action, but I can't specify this as the form's URL, because I won't know which ReadingList to send to at the time the form is created. I could hack it with JavaScript, but I'd rather not rely on that.
Would it be better to have a custom action in the BooksController that accepts a reading list id to add the book to, or can I work the routes so this request ends up getting to the ReadingListController#update action?
I suggest that you have a resource which is a ReadingListEntry that represents a book in a reading list. Then you can simply POST to that resource to add it. There doesn't actually need to be a model behind it, you can manipulate the reading list directly.
Obviously this is something that could easily be achieved by using Ajax to submit the form, but in the case where JavaScript is disabled / unavailable, your best option is to have a custom action in the BooksController that adds it to the required reading list.
You could combine both by having the form pointing to the action in the BooksController, but having an onsubmit handler that posts to the ReadingList controller via Ajax.
I would create a custom action and route such that you can provide a book_id and list_id and form the relation.
Assuming you're using restful routes
resources :books do
post '/lists/:list_id/subscribe' => 'lists#subscribe', :as => :subscribe
end
def subscribe
#list = List.find params[:list_id]
#book = Book.find params[:book_id]
#list << #book
end
Now you can use button_to with or without ajax.
Perhaps a has_many :through relationship would be better? I like Anthony's idea of a ReadingListEntry resource - perhaps put a model behind this giving you:
# models/book.rb
has_many :reading_list_entries
has_many :reading_lists, :through => :reading_list_entries
I think here you are changing the Book, not the ReadingList. Therefore you should PUT to the BooksController#update resource with a new list_id attribute.
# in views/books/show.html.erb
<%= form_for #book, :url => book_path(#book) do |f| =>
<%= f.select :list, ReadingList.all.map { |l| [l.name, l.id] } =>
<%= submit_tag "Change" =>
<% end %>
# in controllers/books_controller.rb
# params[:book][:list_id] => 123
def update
#book = Book.find(params[:id])
#book.update_attributes(params[:book])
end
# config/routes.rb
resources :books
resources :lists do
resources :books
end
If you wanted a Book to belong to more than one ReadingList you'd need a has_and_belongs_to_many relationship instead

Resources