Has_and_belong_to_many association - ruby-on-rails

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

Related

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 can I capture an instance generically?

I'm using Rails 3.2.19 and Ruby 2.1.2. I've been googling around trying to figure this out, but perhaps I'm not searching for the right thing. Anyway, I'll try and be as concise as possible.
I have a few different models that all have a name attribute. In my views I want to somehow be able to access that name attribute regardless of the instance name passed into the view. Currently my various controllers create instances of their respective models. For instance:
class PagesController < ApplicationController
def show
#page = Page.find(params[:id])
respond_to do |format|
format.html
end
end
end
-
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
end
end
end
While I understand I could simply re-name the instances something generic, I was wondering if there was some way of accessing any/all instances while maintaining unambiguous instance names.
Basically something like this:
page.html.haml
%h1= resources[0].name #equates to #page.name
%h2= #page.some_other_attribute
or
product.html.haml
%h1= resources[0].name #equates to #product.name
%h2= #product.price
Where in each of the above resources[0] would be either #page or #product
You will have to define a route with an additional resource_type parameter to a generic controller or otherwise just include the resource_type into the url query parameter
/resources/product/17
or
/resources/17?resource_type=product
This will allow you to do the following in the controller
class ResourcesController < ApplicationController
def show
#resource = find_resource(params)
respond_to do |format|
format.html
end
end
private
def find_resource(params)
resource_klass = {
product: Product,
page: Page
}[params[:resource_type]]
resource_klass.find(params[:id])
end
end
Another Option would be to introduce another ResourceType Entity and define a polymorphic :has_one :belongs_to association to the actual resource entity (product, page). Then always search for ResourceTypes and load the polymorphic resource entity
class ResourceType < ActiveRecord::Base
belongs_to :resource, polymorphic: true
end
class Product < ActiveRecord::Base
has_one :resource_type, as: :resource
end
class Page < ActiveRecord::Base
has_one :resource_type, as: :resource
end
product_resource_type = ResourceType.create(...)
product = Product.create(resource_type: product_resource_type)
page_resource_type = ResourceType.create(...)
page = Page.create(resource_type: page_resource_type)
ResourceType.find(product_resource_type.id).resource
=> product
ResourceType.find(page_resource_type.id).resource
=> page
I figured this out after discovering instance_variables and instance_variables_get
Those methods will return all instance variables being passed into the view. From there I discovered that the :#_assigns instance variable contained the instances that I was looking for. So I iterated over them to find if any had the name attribute.
- instance_variable_get(:#_assigns).each do |var|
- if var[1].respond_to?("name")
%h1= var[1].name
There is probably a better way of accomplishing this, so if anyone has any opinions, they are welcome.

accepts_nested_attributes_for alias?

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

Rails 3 polymorphic associations; does a polymorphic to polymorphic association make sense?

In my app I have users, schools (groups), and courses. I am trying to set up a model whereby any one of these three models can send any other model a request, and give the request-receiver an option to accept or deny the request. The idea I had was to set up a polymorphic to polymorphic association, so rails would identify the request_sender and receiver's id and type. My first question is, does it make sense to set up a poly to poly table to achieve this functionality, or should I be trying something different? If a poly to poly table makes sense I included my current controller and db table:
The database table I set up for requests is:
class CreateRequests < ActiveRecord::Migration
def change
create_table :requests do |t|
t.belongs_to :request_sender, polymorphic: true
t.belongs_to :requestable , polymorphic: true
t.timestamps
end
add_index :requests, [:request_sender_id, :request_sender_type]
add_index :requests, [:requestable_id, :requestable_type]
end
end
this is my request controller, the problem I am having is defining methods to determine the class of both the request sender and receiver:
def new
#request = #requestable.requests.new
end
def create
#request = #requestable.requests.new(params[:request])
if #request.save
redirect_to #requestable, notice: "requested."
else
render :new
end
end
private
def load_requestable
klass = [School, User, Course].detect { |c| params["#{c.name.underscore}_id"] }
#requestable = klass.find(params["#{klass.name.underscore}_id"])
end
any help or guidance is greatly appreciated as I am new to both programming and rails, and have very little clue what I am doing haha.
Sure, no reason why this can't work.
what you'll need when creating a message is to send in the class_name and ID of the creator and receiver. So let's say your form sends in:
receiver_class
receiver_id
sender_class
sender_id
Then, in your MessagesController
def create
message = Message.new.tap do |m|
m.sender = params[:sender_class].classify.constantize.find(params[:sender_id])
m.receiver = params[:receiver_class].classify.constantize.find(params[:receiver_id])
m.message = params[:message]
end
if message.save
redirect_to root_path, notice: "Awesome!"
else
render :new
end
end

Rails 3: alias_method_chain to set specific attribute first

When user's create a post I'd like to set the user_id attribute first. I'm trying to do this using alias_method_chain on the arrtibutes method. But I'm not sure if this is right as the problem I thought this would fix is still occurring. Is this correct?
Edit:
When my users create a post they assign 'artist(s)' to belong to each post, using a virtual attribute called 'artist_tokens'. I store the relationships in an artist model and a joined table of artist_ids and post_ids called artisanships.
I'd like to to also store the user_id of whomever created the artist that belongs to their post (and I want it inside the artist model itself), so I have a user_id column on the artist model.
The problem is when I create the artist for each post and try to insert the user_id of the post creator, the user_id keeps showing as NULL. Which is highly likely because the post's user_id attribute hasn't been set yet.
I figured to get around this I needed to set the user_id attribute of the post first, then let the rest of the attributes be set as they normally are. This is where I found alias_method_chain.
post.rb
attr_reader :artist_tokens
def artist_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Artist.create!(:name => $1, :user_id => self.user_id).id
end
self.artist_ids = ids.split(",")
end
def attributes_with_user_id_first=(attributes = {})
if attributes.include?(:user_id)
self.user_id = attributes.delete(:user_id)
end
self.attributes_without_user_id_first = attributes
end
alias_method_chain :attributes=, :user_id_first
EDIT:
class ArtistsController < ApplicationController
def index
#artists = Artist.where("name like ?", "%#{params[:q]}%")
results = #artists.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
In your controller, why not just do this:
def create
#post = Post.new :user_id => params[:post][:user_id]
#post.update_attributes params[:post]
...
end
But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.
EDIT
I would change this to a callback like this:
class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
#artist_tokens = tokens.split(',')
end
after_save :create_artists
def create_artists
#artist_tokens.each do |token|
...
end
end
end

Resources