Right now my Posts model has_many :tags, :through => :tag_joins
When I add tags, while creating a post, the tag_join records are automatically created.
Now here is what I'm trying to accomplish: While viewing the show view of posts I want to be able to add a new tag.
I tried #post.tag = Tag.new didn't work (returns a "nomethoderror" for tag=)
So I'm trying to figure out how I can add tags and still create those joins automatically.
I am using accepts_nested_attributes etc.
UPDATE: I originally asked how to do this on the index view, but I have changed it to the show view - because I expect it to be a little easier.
You're not too far off with #posts.tags = Tag.new. Here's a couple of ways to do it;
#post.tags << Tag.create(params[:tag])
#post.tags.create params[:tag]
I see a couple of approaches to this problem.. One is to pass through the id of the post with the tag form using either a hidden_field or by using nested routes for tags. Then you can use that in the controller to retrieve the post and use a syntax similar to above.
While that would work, the problem is that it's a bit ugly.. It means your tag controller would be dealing with finding a post (which isn't necessarily wrong, but it shouldn't need to worry about posts. Unless tags can only be associated with posts, that is).
The more graceful way of dealing with it would be to make the form you're showing be a form for the post instance, not a tag. Then you could use nested attributes to create the tag as part of a post.
Take a look at the build_xxx or create_xxx methods that the association (belongs_to, has_many etc) add to the models. You need to create your tag through the post for rails to 'connect' it automatically.
The key observation here is the difference between .new and .create. For my Devour.space application, I was running into the same issue. If you create the object in memory using:
tag = #post.tags.new(tag_params)
tag.save
There will be no tag_joins entry saved to the database. #post.tags will not return your new tag. You must use .create at the moment of instantiation or the association will not be recorded in the JOIN table:
tag = #post.tags.create(tag_params)
#post.tags.last # tag
In my situation, this required a change in how my create action handled requests and errors:
has_many :deck_shares
has_many :decks, through: :deck_shares
....
deck = current_user.decks.new(deck_params)
if deck.save # Does not create entry in DeckShares table
render json: deck
else
render json: deck.errors, as: :unprocessable_entity
end
This became:
begin
deck = current_user.decks.create(deck_params) # creates DeckShare
rescue Exception => e
render json: e, as: :unprocessable_entity
end
render json: deck unless e
Related
I have problem and I don't know how to solve it in a correct way. In my front-end app I have select which shows all products, so I need to send request to my Rails API. Controller has method index that sends all products, but with many different attributes and associations. I don't think so it's a good idea to send request to this method, because in <select> I need only product name and id. Example:
ProductController.rb
def index
render json: #products, include: 'categories, user.company'
end
ProductSerializer.rb
class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :desc, :weight, :amount, ...
belongs_to :user
has_many :categories
end
As u can see ProductSerializer send many things and it is expected but in a different view in FE app. In another page I need only id and name attributes to <select>. I know that I can create new Serializer and add if like this:
def index
render json: #product, each_serializer: ProductSelectSerializer and return if pramas[:select]
render json: #products, include: 'categories, user.company'
end
But I'm not sure is a good idea to create new Serializer only for one request, because in bigger application can be many situations like this. This if in index method dose not looks good too in my opinion, so maybe I should create new method for this request, but it's worth for one small request? Are there any good practices that can help to properly resolve such situations?
I suggest you to try blueprinter. It a gem that help you to serializing your data and this gem is suitable with your needs.
To create the Blueprinter's serializer you can run this command in your terminal :
rails g blueprinter:blueprint Product
After You create the searializer, you may define different outputs by utilizing views:
class ProductBlueprint < Blueprinter::Base
identifier :id
view :normal do
field :product_name
end
view :extended do
fields :product_name, :product_price
association :user, blueprint: UserBlueprint
association :categories, blueprint: CategoryBlueprint
# this will take the association from your product's model and make sure you have created the CategoryBlueprint and UserBlueprint
end
end
After you define the view, now you can use the view in your controller. In your index action, you can call it with this syntax.
def index
render json: ProductBlueprint.render_as_hash(#product, view: :normal) and return if params[:select]
render json: ProductBlueprint.render_as_hash(#products, view: :extended)
end
I have a model Post that has_many of model Comment.
On a Posts show action, there is a list of Comments found with #comments = #post.comments.
I also have a form for creating new Comments. The form has its object created with #comment = #post.comments.build.
This all works for listing and successfully creating comments.
The problem occurs when there is an error when the comment is submitted. The errors are shown in the same form (so, on Post#show) via render "posts/show".
In this case, I have to set #comments = #post.comments again, but this time the list of comments includes the not-yet-saved comment that the user is trying to create.
I solved it by using #post.comments.all, which only gives me the saved models, but Rails complains that this is deprecated in Rails 4.
How do I remove the unsaved comment from the list of comments I get from #post.comments?
I ran into a similar issue with a Rails 5 application. This seemed like the most straightforward approach:
#comment = #post.comments.build
#comments = #post.comments.where.not(id: nil)
You could add a scope to the comment model to find only database rows instead of in memory data, for example:
class Comment < ActiveRecord::Base
scope :by_post, ->(post) { where(post: post) }
end
called by: #comments = Comment.by_post(#post)
The most efficient way imho is just to ignore the new record in the view, instead of retrieving all comments again.
So in your view you will do something like:
= f.simple_fields_for :comments do |c|
- unless c.object.new_record?
= render 'comment_fields', :f => c
You can force load the comments. You have to do it like this:-
#comments = #post.comments(true)
I've been looking, and can't find a good answer for how to delete records in a HABTM table. I assume a lot of people have this same requirement.
Simply, I have Students, Classes, and Classes_Students
I want a student to be able to drop a class, or delete the HABTM record that has signed that student up for that class.
There must be a simple answer to this. Does anyone know what it is?
The reason why .destroy or .delete does not work on this situation is due to the missing primary key in the middle table. However, our parent objects have this really cool method called {other_obj}_ids. It is a collection of ids on the left table object, of the right table object. This information is of course populated from our middle table.
So with that in mind, we have 2 object classes (Student, and Classes). Active record magic can generally figure out the middle table if you are not doing anything fancy, but it is recommended to use has_many :through.
class Student < ActiveRecord::Base
has_and_belongs_to_many :classes
end
class Classes < ActiveRecord::Base
has_and_belongs_to_many :students
end
What we can now do in terms of the middle table with this setup...
student = Student.find_by(1)
student.classes # List of class objects this student currently has.
student.class_ids # array of class object ids this student currently has
# how to remove a course from the middle table pragmatically
course = Course.find_by({:name => 'Math 101'})
# if this is actually a real course...
unless course.nil?
# check to see if the student actually has the course...
if student.class_ids.include?(course.id)
# update the list of ids in the array. This triggers a database update
student.class_ids = student.class_ids - [course.id]
end
end
I know this is a little late to answer this, but I just went through this exact situation tonight and wanted to share the solution here.
Now, if you want this deleted by the form, since you can now see how it is handled pragmatically, simply make sure the form input is nested such that it has something to the effect of:
What kind of trouble are you having? Do you have the appropriate :dependent=>:destroy and :inverse_of=>[foo] on your relations?
Let's say a class had a course title. You can do:
student.classes.find_by_course_title("Science").delete
So the proper answer here is to do something like this in your view:
<%= link_to 'Remove', cycle_cycles_group_path(#cycle, cycle), method: :delete %><br />
cycle is from a block the above code is within.
#cycle is an instance variable from the join models controller.
cycle_cycles_group_path is the nested join table "cycles_groups" under the model "Cycle" in the routes.rb file:
resources :cycles do
resources :cycles_groups do
end
end
and the join model controller looks like this:
def destroy
#cycles_group = CyclesGroup.find(params[:id])
#cycle = #cycles_group.cycle
#cycles_group.destroy
puts "cycle: #{#cycle}"
respond_to do |format|
format.html {redirect_to cycle_path(#cycle), notice: 'Training Week was successfully removed!'}
end
end
I have a few question about MongoID and embedded relations in Rails.
In my model I have:
class Board
include Mongoid::Document
attr_accessible :title
field :title, :type => String
#has_and_belongs_to_many :users
embeds_many :items
end
when I call
Board.all
it returns the whole collection, including also :items.
I've read in many articles/forums that using MongoDB embedded relations should be preferred over referenced ones but I have some questions:
What about performaces? each time i want to retrieve a board i'll also retrieve items inside it: it may be useful sometimes but in the case i want only board's information and not items inside it I should create a new method for not retrieving items.
When I want to update an item the DB will reload the whole document and not only the item I want to retrive, right?
Up to now I've noticed that the only advantage in using embedded document is for something like what in SQL are called "joins" but I also see a lot of performaces problem, there are important reason to use embedded relations over referenced relations?
EDIT
As pointed out by Adam C my thoughts are releated to situations like these:
as explained before I will have Boards each one with many Items inside it and using Rails scaffolding it generates methods that retrieve the whole Board document from the database but many times (for example when editing a Board) i want to load the document without the Items part.
Since I will be using mostly JSON calls my idea was to add an optional parameter to the url like "?get_items" to be set to TRUE in case I want also to get items, in other situations I would use Mongoid's:
Model.without
For example let's take the index action:
def index
#boards = Board.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #boards }
end
end
I'll need to get only fields specified in Board Model (in that case only :title) without items so I may use:
def index
#boards = Board.without :items
respond_to do |format|
format.html # index.html.erb
format.json { render json: #boards }
end
end
That my cause some problems?
If you need to retrieve items separately, then you should not embed them.
My rules of thumb:
Top-level domain objects (things that you work with one their own, that don't always appear in the context of their "parent") should get their own collections.
Embed when the related things
a. Don't grow unbounded. That is, in the 1-N relation, N is bounded.
b. Always (or nearly always) appear with their parent.
You can also embed if you can prove to yourself that the performance improvements to be gained by embedding outweigh the costs of the multiple queries required to obtain all objects.
Neither embedding nor relating should be preferred. They should be considered equally.
tl;dr: Is it possible to intercept posted values from a nested model for further processing? I've tried everything I can think of to access the nested attributes to use for a before_save callback, but that may be only a testament to the limits of my imagination.
I'm writing a library application, where books can have many authors and vice versa. That bit is working just fine with a has_many_through and accepts_nested_attributes_for. The app saves all the book and author information just fine. Except... I can't seem to write a working before_save on either the Author or Book model to check if the Author that we're trying to create exists. Here's what I already have:
class Book < ActiveRecord::Base
has_many :authorships
has_many :authors, :through => :authorships
accepts_nested_attributes_for :authors, :authorships
end
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, :through => :authorships
before_save :determine_distinct_author
def determine_distinct_author
Author.find_or_create_by_author_last( #author )
end
end
## books_controller.rb ##
def new
#book = Book.new
#book.authors.build
respond_to do |format|
format.html
format.xml { render :xml => #book }
end
end
def create
#book = Book.new(params[:book])
#author = #book.authors #I know this line is wrong. This is the problem (I think.)
# more here, obviously, but nothing out of ordinary
end
When I post the form, the dev log passes on this:
Parameters: {"commit"=>"Create Book",\
"authenticity_token"=>"/K4/xATm7eGq/fOmrQHKyYQSKxL9zlVM8aqZrSbLNC4=",\
"utf8"=>"✓", "book"{"title"=>"Test", "ISBN"=>"", "genre_id"=>"1", "notes"=>"", \
"authors_attributes"=>{"0"{"author_last"=>"McKinney", "author_first"=>"Jack"}}, \
"publisher"=>"", "pages"=>"", "language_id"=>"1", "location_id"=>"1"}}
So... the data's there. But how do I get to it to process it? When I post the data, here's that log:
Author Load (0.4ms) SELECT `authors`.* FROM `authors` WHERE\
`authors`.`author_last` IS NULL LIMIT 1
I've done a fair bit of searching around to see what there is out there on nested attributes, and I see a lot on the form side, but not much on how to get to the assets once they're submitted.
Any explanation of why the solution which works does actually work would also be appreciated. Thanks.
First, this line
#author = #book.authors
assigns an array of authors to #author, but in your before_save you seem to be expecting a single author model. Secondly, the #author variable is scoped to the controller and will be empty in your model's scope.
Remove that line and try this in your Book class (not tested):
def determine_distinct_author
authors.each do |author|
Author.find_or_create_by_author_last( author )
end
end
1) The nested attributes is an array, and should be accessed as an array. Getting the submission from the one model to the next still presents quite a problem.
2) accepts_nested_attributes_for is probably not going to help out a whole lot, and there will undoubtedly be a lot of custom processing which will have to happen between now and when the whole system is fully functional. Probably best to ditch it now and move on.
In that direction: It looks like Ryan Bates has done a recent bit on the jQuery TokenInput plugin which will take care of a lot of the autocomplete feature on the author. More to the larger problem (and a bit harder to find), is a follow up that he posted to some issues with the plugin on working with new entries.
I am not quite sure what you are trying to accomplish, but inside your Author you have a before_save callback, and that is obviously giving the error.
To me it seems you are looking for an author with the given name, and then want to use that? Is that correct?
Using a nested form, you will always create a new author, unless you want to use something like a select-box, and then select an author-id (which is part of the book, so not nested), instead of the authors details.
UX-wise, i would offer the option to either select an existing author (use an autocomplete field), or create a new one (using the nested fields option --without your before_save callback).