Testing Datamapper models with RSpec - ruby-on-rails

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.

Related

Searching with multiple conditions (Ruby on Rails)

I want to search a table with multiple conditions in Rails.
I am working on deleting certain package(record) from database but first i need to get userId and packageID .
and here is the code i wrote but it gives error.
#package=Packages.find(:all, :condition => {:id => params[:pid]}, :condition => {:senders_id => cookies[ :user_id ]})
here is the error :
ArgumentError in CreatePackagesController#deletepackage
Unknown key: condition
i just need equivalent code with the right syntax to that one if someone could help.
def deletepackage
#package=Packages.find(:all, :conditions => {:id => params[:pid], :senders_id => cookies[ :user_id ]}
if (#package!=nil && req.receivedByCarrier==false)
#package.destroy
elsif (#package!=nil && req.receivedByCarrier==true)
#package.destroy
end
return;
end
Change your query as below:
#package = Packages.find(:all, :conditions => {:id => params[:pid], :senders_id => cookies[:user_id]})
You are getting the error as Unknown key: condition because :condition is not a valid option in find method.
:condition should be :conditions (Note plural). Also, you should be passing both the conditions as a single key-value pair.
For Rails 4.x
You can simply do it as below
#package = Packages.where(id: params[:pid], senders_id: cookies[:user_id])
This
#package=Packages.find(:all, :condition => {:id => params[:pid]}, :condition => {:senders_id => cookies[ :user_id ]})
should be like this
#package=Packages.find(:all, :conditions => {:id => params[:pid]}, :senders_id => cookies[ :user_id ]})

HAML: Create data-xxx-yyy attribute

I know how to create a link with a data-* attribute:
%a{ :href => "#", :data => { :name ="John", :age => 24 } } Hi John
generates:
Hi John
But how about a 2 deep data name, like data-user-name and data-user-age, a way to group data attributes. As you may guess, I tried:
:data => { :user => { :name => "John", :age => 24 } }
But it doesn't work, giving me strange HTML output:
Hi John
Any idea how to do that? Thanks in advance.
You'll have to use
:data => {'user-name' => 'John', 'user-age' => 24}
The data attribute is special-cased by HAML and it only accounts for shallow values.
Yes, you can! From the simple...
%a(data-user-name="John", data-user-last-name="Arbuckle")
To the complex
%a(data-user-name="#{User.first.name}", data-stack-overflow="all of these will be custom attributes in your link"){href: "garfield.com"}

How to run some check based on the previous status of an instance before that it is stored in the database?

I am using Ruby on Rails 3.2.2 and I would like to run some check based on the previous status of an #article instance before that it is stored in the database. That is, I have to run some method that make use of data already stored in the database related to #article (in the below case that data is {:id => 1, :title => "Sample title", :content => "Sample content", :status => "private"} and not {:id => 1, :title => "Sample title changed!", :content => "Sample content", :status => "private"}).
I thought to proceed "retrieving"/"re-building" the original instance after that its attribute values have changed. That is:
# In the controller file
# #article.attributes
# => {:id => 1, :title => "Sample title", :content => "Sample content", :status => "private"}
#article.update_attributes(params[:article])
# => {:id => 1, :title => "Sample title changed!", :content => "Sample content", :status => "private"}
# In the model file
before_save :retrieve_original
def retrieve_original
# self.attributes
# => {:id => 1, :title => "Sample title changed!", :content => "Sample content", :status => "private"}
original = ... # ?
# original.id
# => nil
# original.attributes
# => {:title => "Sample title", :content => "Sample content", :status => "private"}
# self.attributes
# => {:id => 1, :title => "Sample title changed!", :content => "Sample content", :status => "private"}
end
So, original should be an Article object and just a duplicate of the #article instance (without the id and without that #article attributes have changed).
I tried to play with some Ruby on Rails method (like ActiveRecord::Base.dup or ActiveModel::Dirty.changed_attributes), but I have not found a solution. Furthermore, I think that there's a better way to accomplish what I would like to make compared to my approach. If so, what I might do?
I would like to do that because, for example, if an article is public it should be not possible to change the title value; if the article is private, it should be possible to change the title value. Of course, I have to make that check before to store the edited article data and for all attempts that try to update that data (for this I chose to use a before_save callback). However, if the user try to change the title value and the private value to public, then all checks run in a public "context", but I would like that those checks still run in a private "context" since the previous status was private.
It looks like you can go through the changes hash in your hook. I.e. dup or clone the changed version, then go through changed version's changes and set the attributes back to what they were for your copy.
just save
#article = Article.find(params[:id])`
#orginal = #article
if #article.update_attributes(params[:article])
#original.do_some_method_that_probably_should_be_a_before_update
#.... whatever is next .....
end
Or rethink why you need to do what you think you need to do.
There are a few ways you can do this. Basically, instead of trying to revert the changes afterwords and checking then, it would be easier to make a copy of the object before changes are made.
One way to do this is to create a new object from the 'attributes' of the current object like this:
tmp_article = #article.attributes
Do this before you make any changes to the model object. Then before you save you can use the hash .diff method that's built-in to rails:
diff_hash = tmp_article - #article.attributes
# diff will now contain any differences. It will be empty if they are identical.
Here's a link to the diff extension in Rails:
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/diff.rb

What is the ActiveScaffold syntax for a 'has_many' relation link in list view if placed in the helper?

A 'product' has many 'parallel_products':
class Product < ActiveRecord::Base
has_many :parallel_products, :class_name => "Product", :foreign_key => "master_product_id"
end
In the controller I add the 'parallel_products' column to the list view:
class ProductsController < ApplicationController
active_scaffold :product do |config|
config.list.columns = [ :parallel_products ]
end
end
This gives me a ActiveScaffold generated link in the list view to view, create and edit parallel products of the selected product.
So far so good.
Now, I want to specify this 'parallel_products' link in the helper instead. No changes to the link itself, it should be exactly as the ActiveScaffold generated link. The reason is that I need to add a condition, so that the link is only shown under certain circumstances.
The ActiveScaffold generated link looks like this in the log:
Started GET "/products?assoc_id=6&association=parallel_products&eid=products_6_parallel_products&parent_scaffold=products&adapter=_list_inline_adapter" for 172.16.99.11 at 2012-03-05 09:37:45 +0100
Processing by ProductsController#index as JS
Parameters: {"assoc_id"=>"6", "association"=>"parallel_products", "eid"=>"products_6_parallel_products", "parent_scaffold"=>"products", "adapter"=>"_list_inline_adapter"}
My best proposal for the ActiveScaffold has_many relation link in the helper is:
link_to("link text", :controller => "products", :assoc_id => record.id, :association => "parallel_products", :eid => "products_#{record.id}_parallel_products", :parent_scaffold => "products", :adapter => "_list_inline_adapter")
This gives me in the log:
Started GET "/products?adapter=_list_inline_adapter&assoc_id=6&association=parallel_products&eid=products_6_parallel_products&parent_scaffold=products" for 172.16.99.11 at 2012-03-05 09:39:38 +0100
Processing by ProductsController#index as HTML
Parameters: {"adapter"=>"_list_inline_adapter", "assoc_id"=>"6", "association"=>"parallel_products", "eid"=>"products_6_parallel_products", "parent_scaffold"=>"products"}
My link does not work, but it seems to be very close. Only difference is that the generated link state 'ProductsController#index as JS' and my syntax state 'ProductsController#index as HTML'.
What is the correct ActiveScaffold syntax for making a 'has_many' relation list view link in the helper?
Thanks to Sergio Cambra for helping to solve this.
This is the syntax for a 'has_many' association link if placed in the helper:
link_to("link text", {:controller => "products", :association => "parallel_products",
:parent_scaffold => "products", :product_id => record.id}, :class => 'index as_action',
:id => "as_products-index-parallel_products-#{record.id}-link",
:remote => true, :data => {:position => :after, :action => 'index'})
To answer the question in full, this is how it can be implemented to exactly replace the autogenerated AS association link:
def parallel_products_column(record)
if product.parallel_products.blank?
link_text = "-"
css_class = "index as_action empty"
else
link_text = product.parallel_products[0...3].map{ |p| p.name }.join(", ")
if product.parallel_products.length > 3
link_text += ", … (#{product.parallel_products.length})"
end
css_class = "index as_action"
end
link_to(link_text.html_safe, {:controller => "products", :association => "parallel_products",
:parent_scaffold => "products", :product_id => record.id}, :class => css_class,
:id => "as_products-index-parallel_products-#{record.id}-link",
:remote => true, :data => {:position => :after, :action => 'index'})
end
You will need a small 'hack' for the css 'empty' class, example:
.active-scaffold a.empty {
display: block;
text-align: center;
}
Note:
This is for Rails/AS 3.1 or newer.

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

Resources