The usage of includes method for Ruby on Rails - ruby-on-rails

I don't understand what {article: :categories} means for the below code.
I have read the documentation, but it does not show an example like includes(z => {x: :y}). Can somebody put this to a different form that I can understand? What exactly is x: :y?
#articles = ArticleBase.includes(media.to_sym => { article: :categories })
In addition, if I want to add another condition to it (.includes(media.to_sym => :article)), would the code below be alright syntax-wise?
#articles = ArticleBase.includes(media.to_sym => { article: :categories }, media.to_sym => :article)

Whats happening here is that they are just dynamically assigning a hash key in the hash that's provided as an argument to #includes:
"foo".then do |media|
{ media.to_sym => { article: :categories }}
end
=> {:foo=>{:article=>:categories}}
This is possible when you are using the hash rocket hash syntax but not the colon syntax.
{ media.to_sym: bar } # Raises a SyntaxError
would the code below be alright syntax-wise?
#articles = ArticleBase.includes(media.to_sym => { article: :categories }, media.to_sym => :article)
Not really. It won't raise a syntax error but neither will it do what you're intending. When you have a hash literal with duplicate keys Ruby does not warn you - it just overwrites with the last given value for the key.
"foo".then do |media|
{ media.to_sym => { article: :categories }, media.to_sym => :acticle }
end
# => {:foo=>:acticle}
Its also unclear what this code even is supposed to accomplish since { article: :categories } will include article.

Related

How do you render a partial to a hash value in JBuilder?

I have a tree-like object graph that resembles the following:
{
:name => "Grandparent",
:children => {
:child_a => {
:name => "Parent A",
:children => {
:grandchild_a_a => {
:name => "Child A-A",
:children => {}
}
:grandchild_a_b => {
:name => "Child A-B"
:children => {}
}
}
}
:child_b => {
:name => "Parent B",
:children => {}
}
}
}
I want to generate JSON that mirrors this structure. I don't know how deep the child nesting goes, and the attributes are the same for each level. The keys in the children hash are significant and must be preserved.
I want to use a JBuilder partial to represent a level, and then call it recursively. Here's my template thus far:
# _level_partial.json.jbuilder
# passing the above object graph as :level
json.name level[:name]
json.children do
level[:children].each do |key, child|
# How do I map the following to the given key?
json.partial! "level_partial", :level => child
end
end
I can generate the JSON for each child through the partial call easily enough, but that inserts it directly into the JSON output. How do I map the results of the partial to a particular hash/object key?
I've found an answer. Although it appears to be largely undocumented, JBuilder.set! can accept a block instead of an explicit value. That block can call the partial, which is then assigned to the hash.
json.name level[:name]
json.children do
level[:children].each do |key, child|
json.set! key do
json.partial! "level_partial", :level => child
end
end
end

rails json filter ":only"

I did the following query on my model:
output = user.interests.includes([:culture, :sports])
This gives me all the "interests" with all the "culture" and "sports" entries of the user.
I'd like to forward only the id column for "interests", "culture" and "sports" to the client as json.
I tried doing it this way:
output.to_json(:include => [:culture, :sports], :only => ['id'])
When doing it that way it only shows the IDs of the interests but still includes every column of "culture" and "sports". What do I have to add to restrict "culture" and "sports" also to only the IDs?
Thanks in advance!
You can add options for each of the includes by using a separate hash, e.g:
output.to_json(:include => { :culture => { :only => :id }, :sports => { :only => :id } }, :only => ['id'])

Testing Datamapper models with RSpec

I'm testing a Sinatra application, which is using DataMapper, with RSpec.
The following code:
it "should update the item's title" do
lambda do
post "/hello/edit", :params => {
:title => 'goodbye',
:body => 'goodbye world'
}
end.should change(Snippet, :title).from('hello').to('goodbye')
end
Results in this error:
title should have initially been "hello", but was #<DataMapper::Property::String #model=Snippet #name=:title>
I can of course hack this by removing the lambda and only checking if:
Snippet.first.title.should == 'goodbye'
But that can't be a long term solution since the .first Snippet may not be the same in the future.
Can someone show me the right syntax?
Thanks.
Your spec as written implies that the lambda should actually change the value of the class attribute Snippet.title; I think what you want is something like this:
it "should update the item's title" do
snippet = Snippet.first(:title => "hello")
lambda do
post "/#{snippet.title}/edit", :params => {
:title => 'goodbye',
:body => 'goodbye world'
}
end.should change(snippet, :title).from('hello').to('goodbye')
end
Right?
I finally fixed it using:
it "should update the item's title" do
snippet = Snippet.first(:title => "hello")
post "/hello/edit", :params => {
:title => 'goodbye',
:body => 'goodbye world'
}
snippet.reload.title.should == 'goodbye'
end
Thanks to #Dan Tao whose answer helped me.

How to use jquery-Tokeninput and Acts-as-taggable-on

This is how you use autocomplete with jQuery Tokeninput and ActsAsTaggableOn.
In my situation i am using a nested form but it shouldnt matter. Everything below is code that works.
Code
Product Model:
attr_accessible :tag_list # i am using the regular :tag_list
acts_as_taggable_on :tags # Tagging products
Products Controller:
#1. Define the tags path
#2. Searches ActsAsTaggable::Tag Model look for :name in the created table.
#3. it finds the tags.json path and whats on my form.
#4. it is detecting the attribute which is :name for your tags.
def tags
#tags = ActsAsTaggableOn::Tag.where("tags.name LIKE ?", "%#{params[:q]}%")
respond_to do |format|
format.json { render :json => #tags.map{|t| {:id => t.name, :name => t.name }}}
end
end
Routes:
# It has to find the tags.json or in my case /products/tags.json
get "products/tags" => "products#tags", :as => :tags
Application.js:
$(function() {
$("#product_tags").tokenInput("/products/tags.json", {
prePopulate: $("#product_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
});
Form:
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tags.map(&:attributes).to_json %>
Issue 1(SOLVED)
Must have the line:
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name }}}
Note - You can use #tags.map here as well and you dont have to change the form either.
Below are the 2 issues on why you needed to do this:
I have the following Tag: {"id":1,"name":"Food"}. When I save a Product, tagged "Food", it should save as ID: 1 when it searches and finds the name "Food". Currently, it saves a new Tag with a new ID that references the "Food" ID, i.e. {"id":19,"name":"1"}. Instead, it should be finding the ID, showing the name, and doing a find_or_create_by so it doesn't create a new Tag.
Issue 2(SOLVED)
When I go to products/show to see the tags by doing <%= #product.tag_list %>. The name appears as "Tags: 1", when it really should be "Tags: Food".
How can I fix these issues?
You should define a route in your routes.rb which should handle products/tags path. You can define it like:
get "products/tags" => "products#tags", :as => :tags
Thus should give you a tags_path helper which should evaluate to /products/tags. This should get rid of the errors you mentioned in the question. Be sure to add this route before defining resources :product in your routes.rb
Now onto acts-as-taggable-on, I haven't used this gem, but you should look at method all_tag_counts documentation. Your ProductsController#tags method will need some changes on the following lines. I am not sure if its exactly what would be required, as I use Mongoid and can't test it out.
def tags
#tags = Product.all_tag_counts.(:conditions => ["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", "%#{params[:q]}%"])
respond_to do |format|
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name } }
end
end
little add-on:
If you want to create the tags on the fly, you could do this in your controller:
def tags
query = params[:q]
if query[-1,1] == " "
query = query.gsub(" ", "")
Tag.find_or_create_by_name(query)
end
#Do the search in memory for better performance
#tags = ActsAsTaggableOn::Tag.all
#tags = #tags.select { |v| v.name =~ /#{query}/i }
respond_to do |format|
format.json{ render :json => #tags.map(&:attributes) }
end
end
This will create the tag, whenever the space bar is hit.
You could then add this search setting in the jquery script:
noResultsText: 'No result, hit space to create a new tag',
It's a little dirty but it works for me.
There is a bug in Application.js code. There is an extra ) after "/products/tags.json". Remove the extra ). The code should be:
$("#product_tags").tokenInput("/products/tags.json", {
prePopulate: $("#product_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
I don't know if this is the entirety of your error, but you are not hitting the proper URL with the tokenInput plugin.
This
$("#product_tag_list").tokenInput("/products/tags.json"), {
should be
$("#product_tag_list").tokenInput("/products.json"), {
As I said, I don't know if this is the only problem you are having, but if you change this, does it work?
EDIT:
I have never used ActsAsTaggableOn. Does it create a Tag model for you to use?
From the looks of it on github, if you wanted to query all tags, you might have to use its namespace as opposed to just Tag, meaning ActsAsTaggableOn::Tag. For example, you can see how they access Tags directly in some of the specs.
I had problems with editing the tags if for example the model failed to validate,
I changed
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tags.map(&:attributes).to_json %>
to
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tag_list.map {|tag| {:id => tag, :name => tag } }.to_json %>
If the form failed to validate on first submission, it was creating tags as the ID's of the tags it had created on subsequent submissions.
Two notes: if you're getting the tags changed by numbers on the POST request, use:
tokenValue: "name"
And if you're trying to add non-existent tags, use (undocumented):
allowFreeTagging: true

accepts_nested_attributes_for and nested_form plugin

I've the following code in a _form.html.haml partial, it's used for new and edit actions.
(fyi I use the Ryan Bates' plugin nested_form)
.fields
- f.fields_for :transportations do |builder|
= builder.collection_select :person_id, #people, :id, :name, {:multiple => true}
= builder.link_to_remove 'effacer'
= f.link_to_add "ajouter", :transportations
works fine for the new action...
for the edit action, as explain in the doc, I've to add the :id of already existing associations, so, I've to add something like
= builder.hidden_field :id, ?the value? if ?.new_record?
How can I get the value?
Here is the doc of accepts_nested_attributes_for for reference (source: http://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L332)
# Assigns the given attributes to the collection association.
#
# Hashes with an <tt>:id</tt> value matching an existing associated record
# will update that record. Hashes without an <tt>:id</tt> value will build
# a new record for the association. Hashes with a matching <tt>:id</tt>
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
# matched record for destruction.
#
# For example:
#
# assign_nested_attributes_for_collection_association(:people, {
# '1' => { :id => '1', :name => 'Peter' },
# '2' => { :name => 'John' },
# '3' => { :id => '2', :_destroy => true }
# })
#
# Will update the name of the Person with ID 1, build a new associated
# person with the name `John', and mark the associatied Person with ID 2
# for destruction.
#
# Also accepts an Array of attribute hashes:
#
# assign_nested_attributes_for_collection_association(:people, [
# { :id => '1', :name => 'Peter' },
# { :name => 'John' },
# { :id => '2', :_destroy => true }
# ])
Thanks for your help.
I found my error, here is what i learned fyi:
When you use accepts_nested_attributes_for with many to many associations, keep the :id primary key for the association table.
Cheers
Mine works when using ":_delete" instead of ":_destroy". I am on rails 2.3.4. Ruby 1.8.7
Check out this: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M001605
Nested forms are officially supported with Rails. What you are doing (specifically with the fields_for method) may be conflicting with RAils' built-in way to render fields_for.
Here's the documentation for the Rails way to do fields_for...it's very thorough:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M001605
I highly recommend you try the built-in way instead of the plugin, as that will continue to be supported almost indefinitely.
Hope this helps!

Resources