Targeting announcements Starburst gem - ruby-on-rails

I am using Starburst gem to show announcements upon user login. There is an option to limit the users
Starburst::Announcement.create(
:body => 'Upgrade to platinum and save 10% with coupon code XYZ!',
:limit_to_users =>
[
{
:field => "subscription",
:value => "gold"
}
]
)
Is there any way to pass multiple values for a field like this:
{
:field => "subscription",
:value => ["gold","silver"]
}
Thanks

Currently Starburst doesn't support 'or' in queries. Your best bet is to create a method on your user class and use that for targeting, which will achieve the same result.
Add to the User class:
class User < ActiveRecord::Base
def gold_or_silver
subscription == "gold" || subscription == "silver"
end
end
Then enable the method in config/initializers/starburst.rb (create it if it does not already exist):
Starburst.configuration do |config|
config.user_instance_methods = ["gold_or_silver"] '
end
Then in filtering the message:
Starburst::Announcement.create(
:body => 'Upgrade to platinum and save 10% with coupon code XYZ!',
:limit_to_users =>
[
{
:field => "silver_or_gold",
:value => true
}
]
)

Related

Render array of hashes using rabl

I have the following hash in my controller.
#order = {
:id => "somestringid",
:user_id => "someotherstringid",
:amount => 19.99,
:metadata => [
{
:type => :shipping_data,
:address => "line 1 of address, line 2 of address, city, state, pincode"
},
{
:type => :payment,
:stripe_customer_id => "somestringid",
:stripe_card_id => "someotherstringid"
},
{
:type => :contact,
:email => "someone#example.com",
:phone => "1231231231"
}
]
}
Notes:
The "metadata" is a list of objects.
There can be 0, 1 or more metadata objects.
Each metadata object has a different structure.
The only common key for all metadata objects is the "type" key.
I want to use rabl to generate the following json, but cannot figure out what I should put into my template.
The JSON output that I want should look like the following.
{
"id": "somestringid",
"user_id": "someotherstringid",
"amount": 19.99,
"metadata": [
{
"type": "shipping_data",
"address": "line 1 of address, line 2 of address, city, state, pincode"
},
{
"type": "payment",
"stripe_customer_id": "somestringid",
"stripe_card_id": "someotherstringid"
},
{
"type": "contact",
"email": "someone#example.com",
"phone": "1231231231"
}
]
}
What should I put into my template, so that I get the desired output?
Note
I am not sure whether by rabl you mean you are using standard rabl gem OR rabl-rails because you haven't provided any details about the nature of your application and it is built using what all libraries.
But my solution below is in context of a Rails application using rabl-rails gem. And the rabl-rails gem's README says following:
rabl-rails is faster and uses less memory than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
So I guess it should not be a problem to adapt this solution in context of standard rabl gem whether the application is built Rails or Non-Rails based. My aim is to provide a guidance on the approach which can be used to achieve your desired output.
Now coming to the solution approach:
Using some abstractions you can design a flexible and maintainable solution. Let me elaborate:
Assuming you have a plain ruby-class Order like following or if you don't have any such class you can define it easily using virtus gem which provides some handy out-of-the-box features for a class:
app/models/order.rb
class Order
attr_accessor :id, :user_id, :amount, :order_metadata_obj_arr
....
..
end
app/models/order_metadata.rb
class OrderMetadata
attr_accessor :metadata_type, :metadata_data_obj
...
...
end
app/models/shipping_data.rb
class ShippingData
attr_accessor :address
end
app/models/payment_data.rb
class PaymentData
attr_accessor :stripe_customer_id, :stripe_card_id
end
app/models/contact_data.rb
class ContactData
attr_accessor :email, :phone
end
/app/json_views/orders/metadata/shipping.rabl
attribute :address
/app/json_views/orders/metadata/payment.rabl
attribute :stripe_customer_id, :stripe_card_id
/app/json_views/orders/metadata/contact.rabl
attribute :email, :phone
/app/json_views/orders/order_metadata.rabl
attribute :type
# Anonymous node.
node do |order_metadata_obj|
template_path = "./orders/metadata/#{order_metadata_obj.type}"
metadata_data_obj = order_metadata_obj.metadata_data_obj
partial(template_path, object: metadata_data_obj)
end
/app/json_views/order.rabl
attributes :id, :user_id, :amount
node(:metadata) do |order_obj|
partial('./orders/order_metadata', object: order_obj.order_metadata_obj_arr)
end
Then in your controller do this
/app/controllers/my_controller.rb
class MyController < MyBaseController
def my_action
order_obj = create_order_obj_from_hash(order_hash)
order_json = order_json_representation(order_obj)
status = 200
render json: order_json, status: status
end
private
def create_order_obj_from_hash(order_hash)
order_metadata_arr = order_hash[:metadata]
order_metadata_obj_arr = []
order_metadata_arr.each do |item_hash|
md_type = item_hash[:type]
md_obj = case md_type
when :shipping_data
ShippingData.new(address: item_hash[:address])
when :payment
PaymentData.new(stripe_customer_id: item_hash[:stripe_customer_id], stripe_card_id: item_hash[:stripe_card_id])
when :contact
ContactData.new(email: item_hash[:email], phone: item_hash[:phone])
else
// unsupported md_type
end
unless md_obj.nil?
omd_obj = OrderMetadata.new(metadata_type: md_type, metadata_data_obj: md_obj)
order_metadata_obj_arr << omd_obj
end
end
order_attrs = order_hash.slice(:id, :user_id, :amount)
order = Order.new(order_attrs)
order.order_metadata_obj_arr = order_metadata_obj_arr
order
end
def order_json_representation(order_obj)
template_name = "order.rabl"
template_path = Rails.root.join("app", "json_views")
RablRails.render(order_obj, template_name, view_path: template_path, format: :json)
end
end
Hope this helps.
Thanks.

PayPal-Ruby-SDK web-experience-profile creation

I am a bit of a new rails developer and I am not making sense of Paypal's documentation to create a web experience profile before processing a payment with the REST API:
I can make payments ok with code below as long as I do not try to use the experience profile.
When debugging, I get a value for #webprofile similar to:
#<PayPal::SDK::REST::DataTypes::WebProfile:0x007fe0f9344e50 #error=nil,
#name="YeowZa! T-Shirt Shop", #presentation=#<PayPal::SDK::REST::DataTypes::Presentation:0x007fe0f8ec51b8 #error=nil, #brand_name="YeowZa! Paypa
l", #logo_image="http://www.yeowza.com", #locale_code="US">, #input_fields=#<PayPal::SDK::REST::DataTypes::InputFields:0x007fe0f927b0f0 #error=nil, #allow_note=true, #no_shipping=0, #address_override=1>, #flow_config
=#<PayPal::SDK::REST::DataTypes::FlowConfig:0x007fe0f90808e0 #error=nil, #landing_page_type="billing", #bank_txn_pending_url="http://www.yeowza.com">, #request_id="bcc4bc41-b61c-4d28-94f1-a0912121d8e8", #header={}>
On my console I see:
Request[post]: https://api.sandbox.paypal.com/v1/payment-experience/web-profiles/
Response[400]: Bad Request,
My code so far is:
class PaypalController < ApplicationController
require 'paypal-sdk-rest'
include PayPal::SDK::REST
include PayPal::SDK::Core::Logging
def create
PayPal::SDK::REST.set_config(
:mode => "sandbox", # "sandbox" or "live"
:client_id => "my-id",
:client_secret => "my-secret")
# Build Payment object
#payment = Payment.new({
:intent => "sale",
:payer => {
:payment_method => "paypal"},
:experience_profile_id => self.web_experience,
:redirect_urls => {
:return_url => "http://me.com/paypal_complete",
:cancel_url => "http://me.com/paypal_cancel"},
:transactions => [{
:item_list => {
:items => [{
:name => "me.com Thing",
:sku => "the-specific-horse",
:price => "2",
:currency => "USD",
:quantity => "1" }]},
:amount => {
:total => "2.00",
:currency => "USD" },
:description => "Payment for the-specific-thing" }]
})
# Create Payment and return the status(true or false)
if #payment.create
# Redirect the user to given approval url
#redirect_url = #payment.links.find{|v| v.method == "REDIRECT" }.href
logger.info "Payment[#{#payment.id}]"
logger.info "Redirect: #{#redirect_url}"
redirect_to #redirect_url
else
logger.error #payment.error.inspect
end
end
def paypal_complete
begin
# paymentId: PAY-8L3183743T450642VKWDPH7I
# token: EC-57E34614K6825515M
# PayerID: RBWLMFNFF4ZUC
payment_id = params[:paymentId]
# Retrieve the payment object by calling the
# `find` method
# on the Payment class by passing Payment ID
#payment = Payment.find(payment_id)
logger.info "Got Payment Details for Payment[#{#payment.id}]"
rescue ResourceNotFound => err
# It will throw ResourceNotFound exception if the payment not found
logger.error "Payment Not Found"
end
end
def web_experience
#this is not used right now...don't know how
#webprofile = WebProfile.new(
{
:name => "YeowZa! T-Shirt Shop",
:presentation => {
:brand_name => "YeowZa! Paypal",
:logo_image => "http://www.yeowza.com",
:locale_code => "US"
},
:input_fields => {
:allow_note => true,
:no_shipping => 0,
:address_override => 1
},
:flow_config => {
:landing_page_type => "billing",
:bank_txn_pending_url => "http://www.yeowza.com"
}
})
if #webprofile.create
# Redirect the user to given approval url
logger.info "#webprofile[#{#webprofile}]"
debugger
else
logger.error #payment.error.inspect
debugger
end
end
end
This is old but I thought others might end up here like I did.
I had a similar issue with the .NET SDK. Resolved by removing fields from the request object that I shouldn't have been using i.e. the field bank_txn_pending_url I had set to an empty string, when I didn't define this field at all (which would equate to null), the request returned 200. It clearly states in the documentation that this field should only be used in Germany in certain circumstances.
Once created though, subsequent requests fail, because you cannot have two profiles with the same name. You need to get the list of existing profiles and use the existing ID if found. No need to create over and over.
Bummer the dashboard doesn't capture all requests in the transaction section, would make life much easier if it did.

Creating and populating Redmine custom fields by ruby code

We are developing a mogration from a small issue tracker software to Redmine. We use the Ruby classes directly to migrate the data. The class for an issue is defined like this:
class BuggyIssue < ActiveRecord::Base
self.table_name = :issues
belongs_to :last_issue_change, :class_name => 'BuggyIssueChange', :foreign_key => 'last_issue_change_id'
has_many :issue_changes, :class_name => 'BuggyIssueChange', :foreign_key => 'issue_id', :order => 'issue_changes.date DESC'
set_inheritance_column :none
# Issue changes: only migrate status changes and comments
has_many :issue_changes, :class_name => "BuggyIssueChange", :foreign_key => :issue_id
def attachments
#BuggyMigrate::BuggyAttachment.all(:conditions => ["type = 'issue' AND id = ?", self.id.to_s])
end
def issue_type
read_attribute(:type)
end
def summary
read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
end
def description
read_attribute(:description).blank? ? summary : read_attribute(:description)
end
def time; Time.at(read_attribute(:time)) end
def changetime; Time.at(read_attribute(:changetime)) end
end
Creating an issue and defining custom fields for the issue works. However, populating the custom fields doesn't seem to work. There are 4 custom fields (Contact, Test status, Source and Resolution).
The custom fields are created like this:
repf = IssueCustomField.find_by_name("Contact")
repf ||= IssueCustomField.create(:name => "Contact", :field_format => 'string') if repf.nil?
repf.trackers = Tracker.find(:all)
repf.projects << product_map.values
repf.save!
The values for these fields are passed like this:
i = Issue.new :project => product_map[first_change.product_id],
...
:custom_field_values => {:Contact => issue.contact, 'Test status' => '', :Source => '', :Resolution => ''}
I've also tried a version with an index as hash key:
:custom_field_values => {'1' => issue.contact, 'Test status' => '', :Source => '', :Resolution => ''}
The issue can be saved without an issue, however, no value is ever passed over to Redmine. A
mysql> select count(*) from custom_values where value is not null;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.01 sec)
shows that all values for the custom fields are NULL after the migration. I don't seem to be able to find how this is done correctly, the documentation for the Redmine classes is very sparse.
I spent much time to solve near same issue. Take a look on my code written to transfer data from old system to new via Redmine REST API. Cause I used ActiveResource code will be usable for you.
def update_custom_fields(issue, fields)
f_id = Hash.new { |hash, key| hash[key] = nil }
issue.available_custom_fields.each_with_index.map { |f,indx| f_id[f.name] = f.id }
field_list = []
fields.each do |name, value|
field_id = f_id[name].to_s
field_list << Hash[field_id, value]
end
issue.custom_field_values = field_list.reduce({},:merge)
raise issue.errors.full_messages.join(', ') unless issue.save
end
Now you can just call update_custom_fields(Issue.last, "MyField" => "MyValue" .. and so on)

Rails - Custom Validation For Having A Single Value Once

so I have these two models:
class Tag < ActiveRecord::Base
has_many :event_tags
attr_accessible :tag_id, :tag_type, :value
end
class EventTag < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :event_id, :region
end
and this table for Tags:
**tag_id** **tag_type** **value**
1 "funLevel" "Boring..."
2 "funLevel" "A Little"
3 "funLevel" "Hellz ya"
4 "generic" "Needs less clowns"
5 "generic" "Lazer Tag"
...
What I would like to do is write a custom validation where it checks to see:
Each event_id has only one tag_type of "funLevel" attached to it, but can have more than one "generic" tags
For example:
t1 = EventTag.new(:tag_id => 1, :event_id =>777, :region => 'US')
t1.save # success
t2 = EventTag.new(:tag_id => 2, :event_id =>777, :region => 'US')
t2.save # failure
# because (event_id: 777) already has a tag_type of
# "funLevel" associated with it
t3 = EventTag.new(:tag_id => 4, :event_id =>777, :region => 'US')
t3.save # success, because as (tag_id:4) is not "funLevel" type
I have come up with one ugly solution:
def cannot_have_multiple_funLevel_tag
list_of_tag_ids = EventTag.where("event_id = ?", event_id).pluck(:tag_id)
if(Tag.where("tag_id in ?", list_of_tag_ids).pluck(:tag_type).include? "funLevel")
errors.add(:tag_id, "Already has a Fun Level Tag!")
end
Being new to rails, is there a more better/more elegant/more inexpensive way?
The way you have your data structured means that the inbuilt Rails validations are probably not going to be a heap of help to you. If the funLevel attribute was directly accessible by the EventTag class, you could just use something like:
# event_tag.rb
validate :tag_type, uniqueness: { scope: :event_id },
if: Proc.new { |tag| tag.tag_type == "funLevel" }
(unfortunately, from a quick test you don't seem to be able to validate the uniqueness of a virtual attribute.)
Without that, you're probably stuck using a custom validation. The obvious improvement to the custom validation you have (given it looks like you want to have the validation on EventTag) would be to not run the validation unless that EventTag is a funLevel tag:
def cannot_have_multiple_funLevel_tag
return unless self.tag.tag_type == "funLevel"
...
end

Rails 3 - Seed.rb data for Money Class

I am trying out seeds.rb for the first time, and one of my data models uses encapsulation provided by the money gem.
Relevant gems:
money (3.6.1)
rails (3.0.5)
My model thus far:
app/models/list.rb
class List < ActiveRecord::Base
attr_accessible :alias, :unit, :participating_manufacturer, :quantity
:latest_price_cents, :latest_price_currency, :url
belongs_to :user
composed_of :latest_price,
:class_name => "Money",
:mapping => [%w(latest_price_cents latest_price_cents), %w(latest_price_currency currency_as_string)],
:constructor => Proc.new {
|latest_price_cents, latest_price_currency| Money.new(latest_price_cents ||
0, latest_price_currency || Money.default_currency)
},
:converter => Proc.new {
|value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError,
"Can't convert #{value.class} to Money")
}
end
1) (Addressed successfully)
2) When I get to writing validations, would it be best to write them for the :latest_price attribute or for the :latest_price_cents & :latest_price_currency attributes seperately?
/db/seeds.rb
users = User.create([{ :name => "Foo", :email => "foo#gmail.com",
:password => "foobar", :password_confirmation => "foobar" }])
# etc, will add more users to the array
list = List.create(:user_id => users.first.id, :alias => "Januvia 100mg",
:unit => "tablet", :participating_manufacturer => "Merck",
:quantity => 30, :latest_price_cents => 7500,
:latest_price_currency => "USD", :url =>
"http://www.foobar.com/januvia/100mg-tablets/")
3) Perhaps it is minutiae, but in the seed, should I be assigning values to the virtual :latest_price attribute or to the latest_price_cents and latest_price_currency attributes directly? Is there any way to use faker rather than /db/seeds.rb to perform this task?
I am new to rails and web development.
I can't see your latest_price attribute anywhere, so I'm not sure how to answer your question. Generally, you should validate the attributes entered in the user form. So if a user enters latest_price_cents and latest_price_currency in a form, then they're the ones which need validating.
There's a bug in your seed file. You want to pass in a hash, not an array, when creating a new user; and users should be an array.
users = []
users << User.create!(:name => "Foo",
:email => "foo#gmail.com",
:password => "foobar",)
:password_confirmation => "foobar")
However, if you're considering faker because you want to create some dummy data, take a look at Machinist or Factory Girl. They're designed for creating dummy data, normally for automated tests.
Once you've set up some blueprints, if you want to create dummy data in your seeds file, you can do something like this in seeds.rb:
20.times { List.make } unless Rails.env.production?

Resources