Coming from .NET/Java this was the first concern I had with Rails. Where does business logic go? I was baffled to find out there's no standard way of doing that. Sorry, but putting it into model code always seemed like a hack to me.
Service layer has been the industry standard of coding complex business logic for quite a while now. This vid is great, but it just scratched the surface. Imagine dealing with trees of business logic or testing parts of it in complete isolation by stubbing out the context. Exceptions, transactions, logging all managed automagically using AOP. Sigh... those were the days :)
Next up - moving the instantiation and lifetime management of these service objects to config or DSL using some dependency injection framework.
Just wanted to contribute a bit of info, and mention the Teaspoon project.
I used Jasmine for a long time (in Rails and non-Rails projects), but when Rails 3.0 came out it didn't work well with it, so I switched to Evergreen since it had the features that I needed at the time and used that for well over 2 years, but it lacks nice support for the Rails 3.1 asset pipeline (and it doesn't seem to be maintained anymore?). I switched to JasmineRice fairly recently and used Cucumber to run the javascript suite on CI, but that wasn't great either.
Anyway, I'm not a fan of switching testing frameworks (honestly), and ended up writing a test runner that had all the features that I wanted -- along with several others.
It utilizes the asset pipeline, uses PhantomJS to run headless (or you can use Selenium Webdriver).. It comes with support for Jasmine, Mocha, and QUnit. You can run it via a rake task, or in the browser -- and since it's running via the Rails server itself it can be used in a more integration sort of way -- testing json api's etc.
Anyway, that's my experience, and my attempt at a solid solution. Hopefully the project will help others.
I personally don't think there is. I worked on a rails app where I built the authorisation from scratch with the roles hardcoded and denormalized in a string in my user model.
I had a similar problem, wherein my updated code is not being loaded by unicorn even after reload. Turns out that it's because I have my unicorn config file set preload_app to true, and sending a HUP signal, which unicorn_init.sh uses for reload and restart, would not make unicorn load the code changes (see HUP under http://unicorn.bogomips.org/SIGNALS.html). So I modified deploy.rb to use
%w[start stop reload upgrade].each do |command|
instead and use 'reload' for changes in unicorn.rb and 'upgrade' for rails code changes
Sorry, but you just blew my mind with Page.find_each(&:save)
I knew about find_each for batch loading, but what the heck is &:save and where else can I use it?
I mean, obviously I can see what it's doing, but can I use this anywhere a block is expected? When can't you use this notation?
Need help understanding Ryan's code for the services implementation!
I am reading Ryans code for Service Objects, specifically the PasswordReset class code. It has both the class methods and instance methods plus one instance variable @user which can be accessed via attr_reader class method, so far so good.
Consider the following class method in this service:
password_reset.rb
defself.from_email(email)
new User.find_by_email(email)
end
It shows that the class method is returning a new instance of the "User" class, correct? See how he calls and uses it further in the PasswordResetsController class as shown below:
password_resets_controller.rb
defcreate
password_reset = PasswordReset.from_email(params[:email])
if password_reset.user
password_reset.send_email
redirect_to root_url, notice:"Email sent with password reset instructions."else
redirect_to new_password_reset_url, alert:"Email address does not match a user account."endend
it seems like his call to PassworReset's class method from_email is returning a new instance of the PasswordReset class itself. The method implementation shows that it is returning a new instance of the "User" class however. He then accesses the associated instance variable user via the newly created instance as shown in "if password_reset.user" line of the create method shown above. I am totally confused as to what is going on here. Is this an error? or some "quack quack" (duck typing mumbo jumbo) stuff going on here?
Noob question for the jQuery File Upload setup:
Should i be checking that the image url sent from AWS S3 after user upload is actually from my S3 bucket and not from somewhere else harmful?
In case anyone else had trouble maintaining the current directory when opening a new tab, I wrote the following plugin:
console
# Tell the terminal about the working directory at each prompt.
update_terminal_cwd() {
# Identify the directory using a "file:" scheme URL,# including the host name to disambiguate local vs.# remote connections. Percent-escape spaces.
local SEARCH=''
local REPLACE='%20'
local PWD_URL="file://$HOSTNAME${PWD//$SEARCH/$REPLACE}"
printf '\e]7;%s\a'"$PWD_URL"
}
precmd_functions+='update_terminal_cwd'
Yes, I also came to Rails from Grails. Grails has a strong service layer concept, and encourages to push a lot of business logic into stateless (and transactional) service layer. Something I've missed in Rails, so I started doing something like this implicitly by creating non-db backed model classes.
Also, I would suggest if you are going to use service objects, to use similar convention as with controllers, i.e. call an object authentication_service vs service. That will help to know where the original definition lives - i.e. is it a model or a service.
Using service objects definitely seems cleaner than concerns. As it is, if you have many concerns your model will become fat with include statements :).
I love the service object approach. Your episode on Presenters from scratch really expanded my mind on how to simplify view code using models. I've come to really hate helper modules because they are so hard to test and easy to make a mess with.
For controllers I've been using a set of classes I call ControllerAssistants. I set them up through a before filter and give them info like the params, etc. They are really easy to test and debug.
My models are huge and could really use some help.
Views need Presenters
Controllers need Assistants
Models need Services
Id be very interested in an answer to this. We run a large data warehouse and the amount of models talking to it is becoming to much. I have name spaced it under a Dw class which handles the connection to the DB and any other shared logic. But I wonder if there is a better way to handle such a large quantity of models without cluttering the app models..?
Today I had a big problem with deploying on remote machine. Many hours spent on problem with hanging Capitrano.
When I did cap deploy:install all required packages were installed properly except one - ruby 1.9.3-p327.
Capistrano hangs forever on executing rbenv install 1.9.3-p327 with message Installing ruby-1.9.3-p327....
I've used second ssh connection with top running for checking numer of users logged in. I've detected that message Installing ruby-1.9.3-p327... still exists after number of logged in users decrease from 2 into 1.
What is the reason? SSH timeout session breaks connection.
How to solve the problem? My solution is:
1) Just edit file /etc/ssh/sshd_config and add following lines:
config
ClientAliveInterval 18
ClientAliveCountMax 100
Parameter ClientAliveInterval should have very small number to make pinging very often.
18*100 = 1800 seconds means 30 minutes of session timeout (enough I think).
2) Then restart ssh service:
terminal
sudo service ssh restart
3) Try to deploy:
terminal
cap deploy:install
It works now for me :-)
If you not, additionally try to edit file /etc/ssh/ssh_config:
settings
ServerAliveInterval 18
ServerAliveCountMax 100
Then restart ssh service.
I hope it will be helpfull for somebody.
My first thought also.
Coming from .NET/Java this was the first concern I had with Rails. Where does business logic go? I was baffled to find out there's no standard way of doing that. Sorry, but putting it into model code always seemed like a hack to me.
Service layer has been the industry standard of coding complex business logic for quite a while now. This vid is great, but it just scratched the surface. Imagine dealing with trees of business logic or testing parts of it in complete isolation by stubbing out the context. Exceptions, transactions, logging all managed automagically using AOP. Sigh... those were the days :)
Next up - moving the instantiation and lifetime management of these service objects to config or DSL using some dependency injection framework.
Hi einstyne,
I answered this question on Stackoverflow recently, here's how I did it.
I see that Uncle Bob's talk about architecture in ruby apps is giving an impact on our community !
http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-years
Very helpful screencast. Thanks!
You can just set reasonable defaults in your presenter class's method definitions
On point (+1) reminds me of my java days.. soon we could be singing value objects!!
hi
Just wanted to contribute a bit of info, and mention the Teaspoon project.
I used Jasmine for a long time (in Rails and non-Rails projects), but when Rails 3.0 came out it didn't work well with it, so I switched to Evergreen since it had the features that I needed at the time and used that for well over 2 years, but it lacks nice support for the Rails 3.1 asset pipeline (and it doesn't seem to be maintained anymore?). I switched to JasmineRice fairly recently and used Cucumber to run the javascript suite on CI, but that wasn't great either.
Anyway, I'm not a fan of switching testing frameworks (honestly), and ended up writing a test runner that had all the features that I wanted -- along with several others.
It utilizes the asset pipeline, uses PhantomJS to run headless (or you can use Selenium Webdriver).. It comes with support for Jasmine, Mocha, and QUnit. You can run it via a rake task, or in the browser -- and since it's running via the Rails server itself it can be used in a more integration sort of way -- testing json api's etc.
Anyway, that's my experience, and my attempt at a solid solution. Hopefully the project will help others.
https://github.com/modeset/teaspoon
I personally don't think there is. I worked on a rails app where I built the authorisation from scratch with the roles hardcoded and denormalized in a string in my user model.
app/assets/javascripts/paintings.js.coffee
The video is not working :(
I always browse gems, I MUST know what the gem does for me.
also I made a plugin to browse gems for Sublime Text.
Take a look: https://github.com/NaN1488/sublime-gem-browser
Great episode!
However, has anyone considered the use of "protocols" like Smalltalk does? Any thoughts on pros and cons about using it?
hi
I had a similar problem, wherein my updated code is not being loaded by unicorn even after reload. Turns out that it's because I have my unicorn config file set preload_app to true, and sending a HUP signal, which unicorn_init.sh uses for reload and restart, would not make unicorn load the code changes (see HUP under http://unicorn.bogomips.org/SIGNALS.html). So I modified deploy.rb to use
%w[start stop reload upgrade].each do |command|
instead and use 'reload' for changes in unicorn.rb and 'upgrade' for rails code changes
Thanks. That makes sense.
The contents of the from_email method is short-hand for
Ryan uses implicit parentheses and the fact that you can call other class-level methods without a prefix in a class-level method.
Sorry, but you just blew my mind with
Page.find_each(&:save)
I knew about find_each for batch loading, but what the heck is
&:save
and where else can I use it?I mean, obviously I can see what it's doing, but can I use this anywhere a block is expected? When can't you use this notation?
Hi Guys,
Richard Schneeman is very nice. He posted the article how to implement wicked for another resource.
https://github.com/schneems/wicked/wiki/Building-Partial-Objects-Step-by-Step
Stay tuned :-)
Hi everyone,
if you want to use another resource say "Products", you must
pass the product id in the ProductsController like this:
if @product.save
redirect_to product_step_path(:id => "first_step", :product_id => @ product.id)
else
...
And then you can find this product in the ProductStepsController:
product_steps_controller.rb:
def show
@product = Product.find(params[:product_id])
render_wizard
end
Hope it will help someone out there ;-)
An update on profiling a rails app would be great
Need help understanding Ryan's code for the services implementation!
I am reading Ryans code for Service Objects, specifically the PasswordReset class code. It has both the class methods and instance methods plus one instance variable @user which can be accessed via attr_reader class method, so far so good.
Consider the following class method in this service:
It shows that the class method is returning a new instance of the "User" class, correct? See how he calls and uses it further in the PasswordResetsController class as shown below:
it seems like his call to PassworReset's class method from_email is returning a new instance of the PasswordReset class itself. The method implementation shows that it is returning a new instance of the "User" class however. He then accesses the associated instance variable user via the newly created instance as shown in "if password_reset.user" line of the create method shown above. I am totally confused as to what is going on here. Is this an error? or some "quack quack" (duck typing mumbo jumbo) stuff going on here?
Kindly explain.
Thanks.
Bharat
Noob question for the jQuery File Upload setup:
Should i be checking that the image url sent from AWS S3 after user upload is actually from my S3 bucket and not from somewhere else harmful?
In case anyone else had trouble maintaining the current directory when opening a new tab, I wrote the following plugin:
Then just call the plugin in your
.zshrc
See http://railscasts.com/episodes/114-endless-page-revised?view=comments#comment_161528
You can implement it with the history object, and it will solve problems with the back button, bookmarks etc.
I too have an empty tag_list in my form when editing or updating. If I leave it empty it removes my tags.
I found that the helper_method was not necessary using the american_date gem
http://stackoverflow.com/questions/14012584/convert-standard-rails-ruby-time-to-american-format-dates-getting-mixed-up
This ended up working for me as well.
http://stackoverflow.com/questions/14012584/convert-standard-rails-ruby-time-to-american-format-dates-getting-mixed-up
Yes, I also came to Rails from Grails. Grails has a strong service layer concept, and encourages to push a lot of business logic into stateless (and transactional) service layer. Something I've missed in Rails, so I started doing something like this implicitly by creating non-db backed model classes.
http://grails.org/doc/latest/guide/services.html
Also, I would suggest if you are going to use service objects, to use similar convention as with controllers, i.e. call an object authentication_service vs service. That will help to know where the original definition lives - i.e. is it a model or a service.
Sorry for double post, but that's obviously:
user = User.new user.apply_omniauth(omniauth)
in the authentications controller, not registrations
I'm getting:
undefined method 'apply_omniauth' for #<User:0x007f84ec10b940>
when calling:
in the registrations controller.
Is there a better way of doing this via the methods in Ryans later episode? http://railscasts.com/episodes/235-devise-and-omniauth-revised
good
good
good
+1
Using service objects definitely seems cleaner than concerns. As it is, if you have many concerns your model will become fat with include statements :).
I tried "counter_culture" gem and it is working great. Thank you.
I know, I need it!
Couldn't you use a 'scope' for this?
Edit: @Ryan L. Cross: +1
I love the service object approach. Your episode on Presenters from scratch really expanded my mind on how to simplify view code using models. I've come to really hate helper modules because they are so hard to test and easy to make a mess with.
For controllers I've been using a set of classes I call ControllerAssistants. I set them up through a before filter and give them info like the params, etc. They are really easy to test and debug.
My models are huge and could really use some help.
Views need Presenters
Controllers need Assistants
Models need Services
How great is that?
Id be very interested in an answer to this. We run a large data warehouse and the amount of models talking to it is becoming to much. I have name spaced it under a Dw class which handles the connection to the DB and any other shared logic. But I wonder if there is a better way to handle such a large quantity of models without cluttering the app models..?
Maybe the walk through videos should be free.
Shouldn't this episode be marked as revise? Check out episode 218:
http://railscasts.com/episodes/218-making-generators-in-rails-3
it's cool.. thanks! :)
check out the other Gem I mentioned in a previous comment - it gives full control over delimiters, column headers, etc..
Today I had a big problem with deploying on remote machine. Many hours spent on problem with hanging Capitrano.
When I did
cap deploy:install
all required packages were installed properly except one -ruby 1.9.3-p327
.Capistrano hangs forever on executing
rbenv install 1.9.3-p327
with messageInstalling ruby-1.9.3-p327...
.I've used second ssh connection with
top
running for checking numer of users logged in. I've detected that messageInstalling ruby-1.9.3-p327...
still exists after number of logged in users decrease from 2 into 1.What is the reason? SSH timeout session breaks connection.
How to solve the problem? My solution is:
1) Just edit file
/etc/ssh/sshd_config
and add following lines:Parameter
ClientAliveInterval
should have very small number to make pinging very often.18*100 = 1800 seconds means 30 minutes of session timeout (enough I think).
2) Then restart ssh service:
3) Try to deploy:
It works now for me :-)
If you not, additionally try to edit file
/etc/ssh/ssh_config
:Then restart ssh service.
I hope it will be helpfull for somebody.
I'd like to know this also, I'm often given tab separated files and various text enclosures.
How do I not use delayed_job directly with rails?
I tried to do a little gem about this https://github.com/14113/hello_bar
Perhaps, for someone will be useful.
Just FYI there's a typo in the destroy method
cookies.delete[:secure_user_id] should be cookies.delete(:secure_user_id)