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.
Related
I have two models with no associations between them. So, I generated a migration and added an association.
class Post < ActiveRecord::Base
has_and_belongs_to_many :workspaces
end
class Workspace < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class CreateJoinTablePostsUsers < ActiveRecord::Migration
def change
create_join_table :posts, :workspaces do |t|
# t.index [:post_id, :workspace_id]
# t.index [:workspace_id, :post_id]
end
end
end
I currently have a page where all the posts are shown. However, I added a multi select in the post creation form in order to select one workspace or more when creating. I would like to be able to show only the posts that were created for that particular workspace instead of all of them, as it is at the moment.
My controller is as follows:
class PostsController < Admin::BaseControlle
def index
respond_to do |format|
format.html
format.json do
#posts = Post.all.order(:order)
render json: present_collection(#posts, PostPresenter).map(&:as_json)
end
end
end
private
def post_params
params.require(:post).permit(:label, :url, :active, workspace_ids: [])
end
end
I am able to get the associated workspace this way:
Post.first.workspaces
But I would like to show all the posts and I get an error when I try this command:
Post.all.workspaces
How could I change my controller and accomplish that? Thank you in advance for your help!
Well you should have a table called PostsWorkspaces by rails convention so you should be able to do something like:
posts_in_workspaces = PostsWorkspaces.all.pluck(:post_id)
Posts.where(id: posts_in_workspaces )
The above will return the posts that have at least one workspace associated, the problem with the approach Post.all.workspaces is that not all posts need to have a workspace (or more than one) associated, also you can think on Post.all like select * from posts which is not what you want to accomplish.
Hope the above helps! 👍
You are thinking about this the wrong way. Post.first.workspaces works because the association is applied on the instance of Post returned. But Post.all returns a collection.
Your best bet is to do something like the following.
# Return all posts that have a workspace associated
posts = Post.joins(:workspaces).all.distinct
posts.each do |post|
post.workspaces
end
If you want to include posts without a workspace
posts = Post.includes(:workspaces).all
posts.each do |post|
post.workspaces
end
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.
I'm trying to create a polling system:
# models
class Poll < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :poll
end
# routes
resources :polls do
resources :answers
end
Poll having one string attribute, question, and Answer having one string attribute, answer, and one integer, votes.
I'd like to have it so when a user creates a Poll, a form for a nested Answer instance is displayed. Then have a button with an AJAX call for another form (allowing up to say 10) so multiple answers can be added.
I know how to do the AJAX part, but I don't know how to add child instances to a parent that doesn't exist yet.
The only way I can think of is to use a text_field_tag to pass it through params, then manually create it in the PollsController create method. Sort of like this:
def create
#poll = #poll.create(poll_params)
if #poll.save
if params[:poll_answer1].present?
#poll.answers.create!(answer: params[:poll_answer1])
end
if params[:poll_answer2].present?
#poll.answers.create!(answer: params[:poll_answer2])
end
flash[:notice] = "Poll created."
redirect_to #poll
else
flash[:error] = "Poll could not be created."
render :new
end
end
But it seems like there should be a better way. I'm not opposed to scrapping the entire setup, and maybe placing the questions in an array attribute on Poll instead. But then there's the issue of counting votes.
In Topic model:
class Topic < ActiveRecord::Base
has_many :choices, :dependent => :destroy
accepts_nested_attributes_for :choices
attr_accessible :title, :choices
end
During a POST create, the params submitted is :choices, instead of :choices_attributes expected by Rails, and giving an error:
ActiveRecord::AssociationTypeMismatch (Choice(#70365943501680) expected,
got ActiveSupport::HashWithIndifferentAccess(#70365951899600)):
Is there a way to config accepts_nested_attributes_for to accept params passing as choices instead of choices_attributes in a JSON call?
Currently, I did the attributes creation in the controller (which seems not to be an elegant solution):
def create
choices = params[:topic].delete(:choices)
#topic = Topic.new(params[:topic])
if choices
choices.each do |choice|
#topic.choices.build(choice)
end
end
if #topic.save
render json: #topic, status: :created, location: #topic
else
render json: #topic.errors, status: :unprocessable_entity
end
end
This is an older question, but I just ran into the same problem. Is there any other way around this? It looks like that "_attributes" string is hardcoded in the nested_attributes.rb code (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L337).
Assigning "choices_attributes" to a property when submitting a form is fine, but what if it's being used for an API. In that case it just doesn't make sense.
Does anyone have a way around this or an alternative when passing JSON for an API?
Thanks.
UPDATE:
Well, since I haven't heard any updates on this I'm going to show how I'm getting around this right now. Being new to Rails, I'm open to suggestions, but this is the only way I can figure it out at the moment.
I created an adjust_for_nested_attributes method in my API base_controller.rb
def adjust_for_nested_attributes(attrs)
Array(attrs).each do |param|
if params[param].present?
params["#{param}_attributes"] = params[param]
params.delete(param)
end
end
end
This method basically converts any attributes that are passed in to #{attr}_attributes so that it works with accepts_nested_attributes_for.
Then in each controller that needs this functionality I added a before_action like so
before_action only: [:create] do
adjust_for_nested_attributes(:choices)
end
Right now I'm only worried about creation, but if you needed it for update you could add that into the 'only' clause of the before_action.
You can create method choices= in model as
def choices=(params)
self.choices_attributes = params
end
But you'll break your setter for choices association.
The best way is to modify your form to return choices_attributes instead choices
# Adds support for creating choices associations via `choices=value`
# This is in addition to `choices_attributes=value` method provided by
# `accepts_nested_attributes_for :choices`
def choices=(value)
value.is_a?(Array) && value.first.is_a?(Hash) ? (self.choices_attributes = value) : super
end
I'm learning Rails by writing simple TODO tasks aplication.
Two models are:
class List < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
# ...
end
class Task < ActiveRecord::Base
belongs_to :list
# ...
end
Tasks are routed as a nested resources under Lists. So when a new Task is created by user a POST message is sent to /lists/:list_id/tasks. So far in Tasks#new view's form there is
f.hidden_field :list_id, :value => params[:list_id]
but it's a terrible solution, because anyone can change value of that hidden field.
What is the convention here? Should I put something like
#task.list_id = params[:list_id]
in Tasks#create action and get rid of the hidden field, or maybe
#task = List.find(params[:list_id]).tasks.new(params[:task])
if #task.save
# ...
end
or there is even a better way I don't know about?
Edit:
Yeah, well there was similar question and its answer is pretty much covering my question. If you have different one please post it.
You're right - that would be horrible. No need for hidden fields. Something like the following.
In your TasksController:
def new
#list = List.find(params[:list_id])
#task = #list.tasks.build
end
def create
#list = List.find(params[:list_id])
#task = #list.tasks.new(params[:task])
# etc
end
In your Task#new view:
<% form_for [#list, #task] ... %>
...
<% end %>
If you are concerned about security (like one user creating to-dos in another user's lists - and I assume you are, because you didn't want to use a hidden field stating that anyone can change value of that hidden field), I don't see how #bjg solution is any better then yours, since you're getting #list from params anyways, and anybody can manipulate params on the browser (changing the URL to post to is as easy as changing the hidden field value).
One common way to solve this without having to implement a more complex permission solution is to just use current_user association's, like this:
def new
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.build
end
def create
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.new(params[:task])
# etc
end
This way, no matter what is the value of params[:list_id] (it could have been manipulated by the user), you can rest assured the #task will end up on that user's account, since #list will only find a record that belongs to current_user.
You can evolve this in a real-world app by returning an error message if #list is not found.