Rails 3: draper gem decorating STI models - ruby-on-rails-3

I have STI model
#a/m/document.rb
class Document < ActiveRecord::Base
end
#a/m/document/static.rb
class Document::Static < Document
end
#a/m/document/dynamic.rb
class Document::Dynamic < Document
end
I'm using draper gem to decorate my model
# a/d/document_decorator.rb
class DocumentDecorator < ApplicationDecorator
end
# a/d/document/static_decorator.rb
class Document::StaticDecorator < DocumentDecorator
def foo
'it works 1'
end
end
# a/d/document/dynamic_decorator.rb
class Document::DynamicDecorator < DocumentDecorator
def foo
'it works 2'
end
end
is there posibble way to tell draper to automatically decorate model with propriate STI class decorator ? Like this:
a = Document.last #<Document::Static ...
a.type #Document::Static
b = DocumentDecorator.decorate(a)
b.class # Document::StaticDecorator
b.foo # "it works 1"

Took me while to discover that I can do just
resource.decorate
and it will find propriet decorator
a = Document.last #<Document::Static ...
a.type #Document::Static
b = a.decorate
b.class # Document::StaticDecorator
if you need to explicitly decorate object with Document decorator do this
a = Document.last #<Document::Static ...
a.type #Document::Static
b = DocumentDecorator.decorate a
b.class # DocumentDecorator

Related

Rails: Setting Model Attributes to Attributes from Another Model

I am a little unsure of how to ask this so I apologize for the clunky explanation.
I have three models, User, Waterusage and Goals
class Goal < ApplicationRecord
belongs_to :user
end
class Waterusage < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable,
has_one :waterusage, :dependent => :destroy
has_one :goals, :dependent => :destroy
end
Waterusage is filled out first by users and then goals. Goals is the exactly same schema as waterusage, but uses a portion of the waterusage form and copies the remaining attributes from waterusage.
class Goal < ApplicationRecord
belongs_to :user
# before_validation :get_from_waterusage
before_validation :calculate_totals
def get_from_waterusage
self.household_size = #waterusage.household_size
self.swimming_pool = #waterusage.swimming_pool
self.bathroom_sink_flow_rate = #waterusage.bathroom_sink_flow_rate
self.low_flow_toilet = #waterusage.low_flow_toilet
self.kitchen_sink_usage = #waterusage.kitchen_sink_usage
self.kitchen_sink_flow_rate = #waterusage.kitchen_sink_flow_rate
self.dishwasher_rate = #waterusage.dishwasher_rate
self.dishwasher_multiplier = #waterusage.dishwasher_multiplier
self.laundry_rate = #waterusage.laundry_rate
self.laundry_multiplier = #waterusage.laundry_multiplier
self.lawn_size = #waterusage.lawn_size
self.carwash_rate = #waterusage.carwash_rate
self.carwash_multiplier = #waterusage.carwash_multiplier
self.miles = #waterusage.miles
self.statewater = #waterusage.statewater
self.percent_statewater = #waterusage.percent_statewater
self.pet_cost = #waterusage.pet_cost
end
...
end
Here is the GoalsController
class GoalsController < ApplicationController
before_action :authenticate_user!
def new
#goal = goal.new
end
def create
##user = User.find(params[:user_id])
#goal = current_user.create_goal(goal_params)
redirect_to goal_result_path
end
def destroy
#user = User.find(params[:user_id])
#goal = #user.goal.find(params[:id])
#goal.destroy
redirect_to user_path(current_user)
end
def show
#goal = goal.find(params[:id])
end
def results
if current_user.goal.get_individual_total > 6000
#temp = 6000
else
#temp = current_user.goal.get_individual_total
end
#goal = current_user.goal
end
private
def goal_params
params.require(:goal).permit(:household_size, :average_shower,
:shower_flow_rate, :bath_rate, :bath_multiplier,
:bathroom_sink_usage,
:bathroom_sink_flow_rate, :mellow, :low_flow_toilet,
:kitchen_sink_usage,
:kitchen_sink_flow_rate, :dishwasher_rate,
:dishwasher_multiplier,
:dishwasher_method, :laundry_rate, :laundry_multiplier,
:laundry_method,
:greywater, :lawn_rate, :lawn_multiplier, :lawn_size,
:xeriscaping,
:swimming_pool, :swimming_months, :carwash_rate,
:carwash_multiplier,
:carwash_method, :miles, :statewater, :percent_statewater,
:shopping,
:paper_recycling, :plastic_recycling, :can_recycling,
:textile_recycling,
:diet, :pet_cost, :individual_total, :household_total,
:home_usage, :outdoor_usage,
:individualDifference, :householdDifference, :vehicle_usage,
:power_usage, :indirect_source_usage,
:individualDifference, :householdDifference)
end
end
I currently have the following error:
NameError in GoalsController#create
undefined local variable or method `current_user' for #
<Goal:0x007fbedde9a590>
It seems to be in the way I am retrieving the info from the waterusage model with
self.household_size = #waterusage.household_size
It there a join I could use?
The waterusage model works BTW.
Thanks
Don't know if it's the best way to do that, but I would use something like this:
In your goals model, you can check if its user have a waterusage already. If it has, you fill the values from that water usage
You can do it using after_initialize callback. In your goal model, would be something like
class Goal < ApplicationRecord
belongs_to :user
after_initialize :set_default_values
def set_default_values
waterusage = self.user.try(:waterusage)
if waterusage
self.attribute1 = waterusage.attribute1
self.attribute2 = waterusage.attribute2
self.attribute3 = waterusage.attribute3
#and it goes on...
end
end
end
so, like this when you do a Goal.new, it will check for a waterusage for that user and initialize those values on your goal. So you don't have to change anything on your controller and even if you do it on console, it will work. Guess it's a better practice to do that using models callbacks. Don't know if it solves your problem, but give it a try. Good luck!
Your error message is:
NameError in GoalsController#create
undefined local variable or methodcurrent_user' for #
Goal:0x007fbedde9a590`
The current_user object is automagically defined inside your controller by the Devise gem you're using. It will not be defined inside your models.
One of your comments includes the following snippet you say you're using from within your Goal model: current_user.waterusage.household_size. That is what your error message is referring to. (Note that this snippet from one of your comments disagrees with the code in your original post. This makes it harder to be certain about what is going wrong here.)

Rails 3: How to associate a new Topic with a Forum

I am trying to write a forum with Ruby on Rails.
On model side, I finished association between Topic and Forum
# forum.rb
class Forum < ActiveRecord::Base
has_many :topics
attr_accessible :name, :description
end
# topic.rb
class Topic < ActiveRecord::Base
has_many :posts
belongs_to :forum
end
Controller for Forum
# forums_controller.rb
class ForumsController < ApplicationController
def new
#forum = Forum.new
end
def create
#forum = Forum.new(params[:forum])
if #forum.save
flash[:success] = "Success!"
redirect_to #forum
else
render 'new'
end
end
def index
#forums = Forum.all
end
def show
#forum = Forum.find(params[:id])
end
end
Controller for Topic
class TopicsController < ApplicationController
def new
#topic = current_forum???.topics.build
end
def create
#topic = Topic.new(params[:topic])
if #topic.save
flash[:success] = "Success!"
redirect_to #topic
else
render 'new'
end
end
def index
#topics = Topic.all
end
def show
#topic = Topic.find(params[:id])
end
end
How do I change new and create for topics_controller to make sure the topic is created for current forum rather than some other one?
So for example, if I create a new topic from a forum with id=1, how do I make sure that forum_id=1 for the new topic created?
Using nested resources
resources :forums do
resources :topics
end
you will have a path like
/forums/:forum_id/topics/new
then in your TopicsController
def new
#forum = Forum.find(params[:forum_id])
#topic = #forum.topics.build
end
class TopicsController < ApplicationController
def new
#forum = Forum.find(params[:id])
#topic = #forum.topics.build
end

How can I write this record search in rails 3

#find the most recent news item for a specific locale
def self.find_most_recent_news_for_locale
first(:include => :news_item_detail,:conditions => ["news_items.created_at= ? AND news_item_details.locale = ?", maximum(:created_at), I18n.locale])
end
The Models
class NewsItem < ActiveRecord::Base
has_one :news_item_detail
end
class NewsItemDetail < ActiveRecord::Base
belongs_to :news_item
end
In Rails 3, you should use the where method for creating conditions:
def self.find_most_recent_news_for_locale
where("news_items.created_at = ? AND news_item_details.locale = ?",
maximum(:created_at),
I18n.locale).includes(:news_item_details).first
end
You might want to watch episode #202 of Railscasts as a quick tutorial.

Ruby on Rails - Simplifying similar methods that access different variables

I'm working on a fairly simple site that allows users to choose recipe ingredients, their quantities and then shows them nutritional info based on their recipe and a large database.
Right now, I feel like I'm repeating myself a bit. I want to be able to make this "DRY" by having one method each in the Recipe and Recipe_Ingredient model that will do the same thing only accept the right parameter, which will be the type of nutrient.
Here is the relevant code in my view that currently calls two different methods (and will call more when extended to the other nutrients):
<ul>Calories <%= #recipe.total_calories %></ul>
<ul>Fat (grams) <%= #recipe.total_fat %></ul>
In my recipe model, I have methods that iterate over each of the ingredients in the recipe:
def total_calories
recipe_ingredients.to_a.sum { |i| i.total_calories }
end
def total_fat
recipe_ingredients.to_a.sum { |i| i.total_fat }
end
In the block, we call two separate methods that actually calculate the nutrients for each individual recipe ingredient:
def total_calories
ingredient.calories*ingredient.weight1*quantity/100
end
def total_fat
ingredient.fat*ingredient.weight1*quantity/100
end
This last piece is where we reference the database of ingredients. For context, here are the relationships:
class RecipeIngredient < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
class Recipe < ActiveRecord::Base
has_many :recipe_ingredients
Thanks in advance for any help.
Lev
The send method with a symbol parameter works well for that kind of DRY.
<ul>Calories <%= #recipe.total :calories %></ul>
<ul>Fat (grams) <%= #recipe.total :fat %></ul>
Recipe
def total(type)
recipe_ingredients.to_a.sum { |i| i.total type }
end
RecipeIngredient
def total(type)
ingredient.send(type) * ingredient.weight1 * quantity / 100
end
You could use meta programming to dynamically add the methods. Here is a start, you can get even more DRY than this.
class DynamicTotalMatch
attr_accessor :attribute
def initialize(method_sym)
if method_sym.to_s =~ /^total_of_(.*)$/
#attribute = $1.to_sym
end
end
def match?
#attribute != nil
end
end
Recipe
class Recipe
def self.method_missing(method_sym, *arguments, &block)
match = DynamicTotalMatch.new(method_sym)
if match.match?
define_dynamic_total(method_sym, match.attribute)
send(method_sym, arguments.first)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
if DynamicTotalMatch.new(method_sym).match?
true
else
super
end
end
protected
def self.define_dynamic_total(method, attribute)
class_eval <<-RUBY
def self.#{method}(#{attribute})
recipe_ingredients.to_a.sum { |i| i.send(attribute)
end
RUBY
end
end
RecipeIngredient
class RecipeIngredient
def self.method_missing(method_sym, *arguments, &block)
match = DynamicTotalMatch.new(method_sym)
if match.match?
define_dynamic_total(method_sym, match.attribute)
send(method_sym, arguments.first)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
if DynamicTotalMatch.new(method_sym).match?
true
else
super
end
end
protected
def self.define_dynamic_total(method, attribute)
class_eval <<-RUBY
def self.#{method}(#{attribute})
ingredient.send(attribute) * ingredient.weight1 * quantity / 100
end
RUBY
end
end
Example was copied from ActiveRecord and this page: http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/

Rails 3 : Add some methods in models, for development

I would like to add some methods to some AR Models of my App; but I think they should only be available under some circumstances; this requires some meta-programming.
So I'd like to have a file where I put all my debug methods, the only question is where to put it?
Example, I have the models:
class Admin::Restaurant < ActiveRecord::Base
class Admin::Order < ActiveRecord::Base
And in my file I have (it does deppend on MetaWhere.operator_overload! initialization):
if Rails.env != 'production'
class Admin::Order
def self.mock_make
r = Restaurant.first
user_query = User.where( :created_at > "2011-04-01" )
u = user_query.first( :offset => ( user_query.count * rand ).to_i )
o = r.orders.new
o.user = u
o.value = rand(100) + rand.round(2)
if o.save
return o
else
return nil
end
end
end
end
The thing is.. I can't get it to work on /config/initializers or /app/models.
Wrap it as a external module and include it with if condition
class MyClass << ActiveRecord::Base
include MyExtraModule if Rails.env == 'development'
end
Put them in config/environments/development.rb